diff --git a/src/fr/animesama/AndroidManifest.xml b/src/fr/animesama/AndroidManifest.xml new file mode 100644 index 000000000..30deb7f79 --- /dev/null +++ b/src/fr/animesama/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/src/fr/animesama/build.gradle b/src/fr/animesama/build.gradle new file mode 100644 index 000000000..f348cfeaf --- /dev/null +++ b/src/fr/animesama/build.gradle @@ -0,0 +1,11 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'AnimeSama' + pkgNameSuffix = 'fr.animesama' + extClass = '.AnimeSama' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/fr/animesama/res/mipmap-hdpi/ic_launcher.png b/src/fr/animesama/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..039017b8c Binary files /dev/null and b/src/fr/animesama/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/fr/animesama/res/mipmap-mdpi/ic_launcher.png b/src/fr/animesama/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..afc96e0e9 Binary files /dev/null and b/src/fr/animesama/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/fr/animesama/res/mipmap-xhdpi/ic_launcher.png b/src/fr/animesama/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..139d16ecb Binary files /dev/null and b/src/fr/animesama/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/fr/animesama/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/animesama/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..9b9283a2b Binary files /dev/null and b/src/fr/animesama/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/fr/animesama/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/animesama/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..48eb6a24e Binary files /dev/null and b/src/fr/animesama/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/fr/animesama/res/web_hi_res_512.png b/src/fr/animesama/res/web_hi_res_512.png new file mode 100644 index 000000000..b1112b4c0 Binary files /dev/null and b/src/fr/animesama/res/web_hi_res_512.png differ diff --git a/src/fr/animesama/src/eu/kanade/tachiyomi/extension/fr/animesama/AnimeSama.kt b/src/fr/animesama/src/eu/kanade/tachiyomi/extension/fr/animesama/AnimeSama.kt new file mode 100644 index 000000000..3584303f2 --- /dev/null +++ b/src/fr/animesama/src/eu/kanade/tachiyomi/extension/fr/animesama/AnimeSama.kt @@ -0,0 +1,171 @@ +package eu.kanade.tachiyomi.extension.fr.animesama + +import android.net.Uri +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.FilterList +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 kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy + +class AnimeSama : ParsedHttpSource() { + + override val name = "AnimeSama" + + 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/" + + override val lang = "fr" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient + + private val json: Json by injectLazy() + + override fun headersBuilder(): Headers.Builder = super.headersBuilder() + .add("Accept-Language", "fr-FR") + + // Popular + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/scan", headers) + } + + override fun popularMangaSelector() = "figure.figure" + + override fun popularMangaFromElement(element: Element): SManga { + return SManga.create().apply { + title = element.select("figcaption").text() + setUrlWithoutDomain(element.select("a").attr("href")) + thumbnail_url = element.select("a > img").attr("src") + } + } + + override fun popularMangaNextPageSelector(): String? = null + + // Latest + override fun latestUpdatesRequest(page: Int): Request { + return GET(baseUrl, headers) + } + + override fun latestUpdatesSelector() = "div.container-fluid:nth-child(15) > div:nth-child(1) figure" + + override fun latestUpdatesFromElement(element: Element): SManga { + 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 + + // Search + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val uri = Uri.parse("$baseUrl/search/search.php").buildUpon() + .appendQueryParameter("terme", query + " [SCANS]") + .appendQueryParameter("s", "Search") + return GET(uri.toString(), headers) + } + override fun searchMangaSelector() = "div.media-body" + override fun searchMangaNextPageSelector(): String? = null + override fun searchMangaFromElement(element: Element): SManga { + return SManga.create().apply { + title = element.select("h5").text() + setUrlWithoutDomain(element.select("a").attr("href")) + thumbnail_url = + cdn_url + title.replace( + " [SCANS]", + "" + ).replace(" ", "-").trim() + "carre.jpg" + } + } + + // Details + 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("div.carousel-item:nth-child(2) > div:nth-child(2) > p:nth-child(1)").text() + thumbnail_url = document.select("div.carousel-item:nth-child(1) > img:nth-child(1)").attr("src") + genre = document.select("div.carousel-item:nth-child(2) > div:nth-child(2) > p:nth-child(2)").text().replace("Genres : ", "") + } + + // Chapters + override fun chapterListSelector() = throw Exception("Not used") + + override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used") + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + val javascriptUrl = document.select("body > script:nth-child(3)").attr("abs:src") + + val newHeaders = headersBuilder() + .add("Accept-Version", "v1") + .build() + + val request = GET(javascriptUrl, newHeaders) + val responsejs = client.newCall(request).execute() + val jsonDataString = responsejs.body?.string().orEmpty() + + return jsonDataString + .split(" ", ",") + .filter { it.contains("eps") && !it.contains("drive.google.com") } + .mapNotNull { it.replace("=", "").replace("eps", "").toIntOrNull() } + .sorted() + .map { chapter -> + SChapter.create().apply { + name = "Chapitre $chapter" + setUrlWithoutDomain(javascriptUrl + "/$chapter") + } + } + } + + // Pages + override fun pageListParse(document: Document): List { + val url = document.baseUri().split("/") + val javascriptUrlFinal = url.subList(0, url.size - 1).joinToString("/") + val javascriptResponse = checkJavascript(javascriptUrlFinal) + val jsonDataString = javascriptResponse.body?.string().orEmpty() + + val episode = url[url.size - 1] + val allEpisodes = jsonDataString.split("var") + + val theEpisode = allEpisodes.firstOrNull { it.contains("eps$episode") } + ?: return emptyList() + + val final_list = theEpisode + .substringAfter("[") + .substringBefore("]") + + return final_list + .substring(0, final_list.lastIndexOf(",")).replace("""'""".toRegex(), "\"") + .let { json.decodeFromString>("[$it]") } + .mapIndexed { i, imageUrl -> Page(i, "", imageUrl) } + } + + private fun checkJavascript(url: String): Response { + val request = GET(url, headers) + + return client.newCall(request).execute() + } + + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") + + override fun imageRequest(page: Page): Request { + val imgHeaders = headersBuilder() + .add("Referer", baseUrl) + .build() + + return GET(page.imageUrl!!, imgHeaders) + } +}