diff --git a/src/fr/nekosama/build.gradle b/src/fr/nekosama/build.gradle index 978dc134a..8f41199eb 100644 --- a/src/fr/nekosama/build.gradle +++ b/src/fr/nekosama/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'NekoSama' pkgNameSuffix = 'fr.nekosama' extClass = '.NekoSama' - extVersionCode = 1 + extVersionCode = 2 libVersion = '13' containsNsfw = false } diff --git a/src/fr/nekosama/src/eu/kanade/tachiyomi/animeextension/fr/nekosama/NekoSama.kt b/src/fr/nekosama/src/eu/kanade/tachiyomi/animeextension/fr/nekosama/NekoSama.kt index 53530e658..5ff53b141 100644 --- a/src/fr/nekosama/src/eu/kanade/tachiyomi/animeextension/fr/nekosama/NekoSama.kt +++ b/src/fr/nekosama/src/eu/kanade/tachiyomi/animeextension/fr/nekosama/NekoSama.kt @@ -9,12 +9,16 @@ import eu.kanade.tachiyomi.animeextension.fr.nekosama.extractors.StreamTapeExtra import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.model.AnimeFilter import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +import eu.kanade.tachiyomi.animesource.model.AnimesPage import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -23,6 +27,7 @@ import org.jsoup.nodes.Document import org.jsoup.nodes.Element import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import java.lang.Exception class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() { @@ -37,6 +42,8 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val client: OkHttpClient = network.cloudflareClient + private val json: Json by injectLazy() + private val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } @@ -61,11 +68,16 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun episodeListParse(response: Response): List { val pageBody = response.asJsoup() - return pageBody.select("div.row.no-gutters.js-list-episode-container div.col-lg-4.col-sm-6.col-xs-6").map { + val episodesJson = pageBody.selectFirst("script:containsData(var episodes =)").data() + .substringAfter("var episodes = ").substringBefore(";") + val json = json.decodeFromString>(episodesJson) + + return json.map { SEpisode.create().apply { - url = it.select("div div.text a").attr("href") - episode_number = it.select("div div.text a span").text().substringAfter(". ").toFloat() - name = it.select("div div.text a span").text() + name = try { it.episode!! } catch (e: Exception) { "episode" } + url = it.url!!.replace("\\", "") + + episode_number = try { it.episode!!.substringAfter(". ").toFloat() } catch (e: Exception) { (0..10).random() }.toFloat() } } } @@ -81,14 +93,13 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() { val script = document.selectFirst("script:containsData(var video = [];)").data() val firstVideo = script.substringBefore("else {").substringAfter("video[0] = '").substringBefore("'").lowercase() val secondVideo = script.substringAfter("else {").substringAfter("video[0] = '").substringBefore("'").lowercase() - when { firstVideo.contains("streamtape") -> StreamTapeExtractor(client).videoFromUrl(firstVideo, "StreamTape")?.let { videoList.add(it) } - firstVideo.contains("pstream") -> videoList.add(PstreamExtractor(firstVideo)) + firstVideo.contains("pstream") -> videoList.add(pstreamExtractor(firstVideo)) } when { secondVideo.contains("streamtape") -> StreamTapeExtractor(client).videoFromUrl(secondVideo, "StreamTape")?.let { videoList.add(it) } - secondVideo.contains("pstream") -> videoList.add(PstreamExtractor(secondVideo)) + secondVideo.contains("pstream") -> videoList.add(pstreamExtractor(secondVideo)) } return videoList } @@ -120,9 +131,14 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { val filterList = if (filters.isEmpty()) getFilterList() else filters val typeFilter = filterList.find { it is TypeFilter } as TypeFilter + val typeSearch = when (typeFilter.toUriPart()) { + "anime" -> "vostfr" + "anime-vf" -> "vf" + else -> "vostfr" + } return when { - query.isNotBlank() -> throw Exception("Recherche de texte non prise en charge") + query.isNotBlank() -> GET("$baseUrl/animes-search-$typeSearch.json?$query") typeFilter.state != 0 || query.isNotBlank() -> when (page) { 1 -> GET("$baseUrl/${typeFilter.toUriPart()}") else -> GET("$baseUrl/${typeFilter.toUriPart()}/$page") @@ -134,19 +150,51 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() { } } - override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element) + override fun searchAnimeParse(response: Response): AnimesPage { + val pageUrl = response.request.url.toString() + val query = pageUrl.substringAfter("?").lowercase() - override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() + return when { + pageUrl.contains("animes-search") -> { + val jsonSearch = json.decodeFromString>(response.asJsoup().body().text()) + val animes = mutableListOf() + jsonSearch.map { + if (it.title!!.lowercase().contains(query)) { + val animeResult = SAnime.create().apply { + url = it.url!! + title = it.title!! + thumbnail_url = try { + it.url_image + } catch (e: Exception) { + "$baseUrl/images/default_poster.png" + } + } + animes.add(animeResult) + } + } + AnimesPage( + animes, false + ) + } + else -> { + AnimesPage( + response.asJsoup().select(popularAnimeSelector()).map { popularAnimeFromElement(it) }, true + ) + } + } + } - override fun searchAnimeSelector(): String = popularAnimeSelector() + override fun searchAnimeFromElement(element: Element): SAnime = throw Exception("not used") + + override fun searchAnimeNextPageSelector(): String = throw Exception("not used") + + override fun searchAnimeSelector(): String = throw Exception("not used") override fun animeDetailsParse(document: Document): SAnime { val anime = SAnime.create() anime.title = document.selectFirst("div.col.offset-lg-3.offset-md-4 h1").text() anime.description = document.select("div.synopsis p").text() anime.thumbnail_url = document.select("div.cover img").attr("src") - val a = document.select("div.cover img") - return anime } @@ -196,8 +244,8 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() { screen.addPreference(videoQualityPref) } - private fun PstreamExtractor(url: String): Video { - val noVideo = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4" + private fun pstreamExtractor(url: String): Video { + val noVideo = "http://discloud-storage.herokuapp.com/file/cf781d7d4d02a84b85620ed9ddf7066b/amogus.mp4" val document = Jsoup.connect(url).headers( mapOf( "Accept" to "*/*", @@ -207,7 +255,6 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() { ) ).get() document.select("script").forEach { Script -> - if (Script.attr("src").contains("https://www.pstream.net/u/player-script")) { val playerScript = Jsoup.connect(Script.attr("src")).headers( mapOf( @@ -237,6 +284,36 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() { } return Video(noVideo, "NO VIDEO", noVideo) } + + @Serializable + data class EpisodesJson( + var time: String? = null, + var episode: String? = null, + var title: String? = null, + var url: String? = null, + var url_image: String? = null + + ) + + @Serializable + data class SearchJson( + var id: Int? = null, + var title: String? = null, + var titleEnglish: String? = null, + var titleRomanji: String? = null, + var titleFrench: String? = null, + var others: String? = null, + var type: String? = null, + var status: String? = null, + var popularity: Double? = null, + var url: String? = null, + var genres: ArrayList = arrayListOf(), + var url_image: String? = null, + var score: String? = null, + var startDateYear: String? = null, + var nbEps: String? = null + + ) } /*