diff --git a/src/fr/invinciblecomics/build.gradle b/src/fr/invinciblecomics/build.gradle new file mode 100644 index 000000000..7c4f57c64 --- /dev/null +++ b/src/fr/invinciblecomics/build.gradle @@ -0,0 +1,7 @@ +ext { + extName = 'Invincible ComicsVF' + extClass = '.InvincibleComics' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/fr/invinciblecomics/res/mipmap-hdpi/ic_launcher.png b/src/fr/invinciblecomics/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..20c73ff84 Binary files /dev/null and b/src/fr/invinciblecomics/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/fr/invinciblecomics/res/mipmap-mdpi/ic_launcher.png b/src/fr/invinciblecomics/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..b4ab246c8 Binary files /dev/null and b/src/fr/invinciblecomics/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/fr/invinciblecomics/res/mipmap-xhdpi/ic_launcher.png b/src/fr/invinciblecomics/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..7b3460ab9 Binary files /dev/null and b/src/fr/invinciblecomics/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/fr/invinciblecomics/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/invinciblecomics/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..63c6166a4 Binary files /dev/null and b/src/fr/invinciblecomics/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/fr/invinciblecomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/invinciblecomics/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..71c7e61ed Binary files /dev/null and b/src/fr/invinciblecomics/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/fr/invinciblecomics/src/eu/kanade/tachiyomi/extension/fr/invinciblecomics/InvincibleComics.kt b/src/fr/invinciblecomics/src/eu/kanade/tachiyomi/extension/fr/invinciblecomics/InvincibleComics.kt new file mode 100644 index 000000000..3c3b6d468 --- /dev/null +++ b/src/fr/invinciblecomics/src/eu/kanade/tachiyomi/extension/fr/invinciblecomics/InvincibleComics.kt @@ -0,0 +1,132 @@ +package eu.kanade.tachiyomi.extension.fr.invinciblecomics + +import eu.kanade.tachiyomi.network.GET +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 okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Element + +class InvincibleComics : HttpSource() { + override val name = "Invincible ComicsVF" + + override val baseUrl = "https://invinciblecomicsvf.fr" + + override val lang = "fr" + + override val supportsLatest = true + + // Popular + override fun popularMangaRequest(page: Int): Request { + return GET(baseUrl, headers) + } + + override fun popularMangaParse(response: Response): MangasPage { + val mangas = response.asJsoup().select(".top-comics-grid .comic-card, .top-bd .comic-card").map { element -> + parseMangaFromElement(element) + } + + return MangasPage(mangas, false) + } + + // Latest + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) + + override fun latestUpdatesParse(response: Response): MangasPage { + val mangas = response.asJsoup().select(".nouveaux-ajouts .comic-card").map { element -> + parseMangaFromElement(element) + } + + return MangasPage(mangas, false) + } + + private fun parseMangaFromElement(element: Element): SManga = SManga.create().apply { + title = element.selectFirst("h3")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + thumbnail_url = element.selectFirst("img")?.parseSrcset() + } + + // Search + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = "$baseUrl/page/$page/?s=$query&ct_post_type=comic:bande_dessine".toHttpUrl() + + return GET(url, headers) + } + + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(".entry-card").map { element -> + SManga.create().apply { + title = element.selectFirst("a")?.attr("aria-label")!! + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + thumbnail_url = element.selectFirst("img")?.attr("data-src") + } + } + + return MangasPage(mangas, document.selectFirst(".next") != null) + } + + // Details + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + return SManga.create().apply { + thumbnail_url = document.selectFirst("#main img")?.parseSrcset() + description = document.selectFirst("strong:contains(Résumé)")?.parent()?.ownText() + author = document.selectFirst("strong:contains(Auteur)")?.parent()?.ownText() + genre = document.selectFirst("strong:contains(Genres)")?.parent()?.ownText() + status = when (document.selectFirst("strong:contains(Statut)")?.parent()?.ownText()) { + "En cours" -> SManga.ONGOING // OK + "Terminé" -> SManga.COMPLETED // TODO: Check if this is correct + else -> SManga.UNKNOWN + } + } + } + + // Chapters + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + return document.select("a.tome-link").map { element -> + SChapter.create().apply { + name = element.text() + setUrlWithoutDomain(element.attr("href")) + date_upload = 0L + chapter_number = element.attr("data-tome").toFloatOrNull() ?: 0f + } + }.reversed() + } + + // Pages + override fun pageListParse(response: Response): List { + val document = response.asJsoup() + + val script = document.selectFirst("script:containsData(imageBase)")?.data() + ?: error("No script with imageBase found") + val imageBaseUrl = Regex("""imageBase = "(.*)";""").find(script)?.groupValues?.get(1) + ?: error("Failed to extract imageBase URL from script") + val totalPages = Regex("""const totalPages = (\d+);""").find(script)?.groupValues?.get(1) + ?: error("Failed to extract total pages from script") + + return (1..totalPages.toInt()).map { pageNumber -> + Page(pageNumber, imageUrl = "$imageBaseUrl${"%03d".format(pageNumber)}.png") + } + } + + // Page + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() + + // Utils + private fun Element.parseSrcset(): String { + if (!this.hasAttr("data-srcset")) { + return this.attr("data-src") + } + val parts = this.attr("data-srcset").split(",").map { it.trim() } + return parts.last { it.contains(" ") }.substringBefore(" ") + } +}