Fix episodes and added search [Neko-Sama] (#786)
This commit is contained in:
committed by
GitHub
parent
225eaacc02
commit
34fb9ba2bd
@ -1,11 +1,12 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
extName = 'NekoSama'
|
extName = 'NekoSama'
|
||||||
pkgNameSuffix = 'fr.nekosama'
|
pkgNameSuffix = 'fr.nekosama'
|
||||||
extClass = '.NekoSama'
|
extClass = '.NekoSama'
|
||||||
extVersionCode = 1
|
extVersionCode = 2
|
||||||
libVersion = '13'
|
libVersion = '13'
|
||||||
containsNsfw = false
|
containsNsfw = false
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,16 @@ import eu.kanade.tachiyomi.animeextension.fr.nekosama.extractors.StreamTapeExtra
|
|||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
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.SAnime
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@ -23,6 +27,7 @@ import org.jsoup.nodes.Document
|
|||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
|
|
||||||
class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||||
@ -37,6 +42,8 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
private val preferences: SharedPreferences by lazy {
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
}
|
}
|
||||||
@ -61,11 +68,16 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||||
val pageBody = response.asJsoup()
|
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<List<EpisodesJson>>(episodesJson)
|
||||||
|
|
||||||
|
return json.map {
|
||||||
SEpisode.create().apply {
|
SEpisode.create().apply {
|
||||||
url = it.select("div div.text a").attr("href")
|
name = try { it.episode!! } catch (e: Exception) { "episode" }
|
||||||
episode_number = it.select("div div.text a span").text().substringAfter(". ").toFloat()
|
url = it.url!!.replace("\\", "")
|
||||||
name = it.select("div div.text a span").text()
|
|
||||||
|
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 script = document.selectFirst("script:containsData(var video = [];)").data()
|
||||||
val firstVideo = script.substringBefore("else {").substringAfter("video[0] = '").substringBefore("'").lowercase()
|
val firstVideo = script.substringBefore("else {").substringAfter("video[0] = '").substringBefore("'").lowercase()
|
||||||
val secondVideo = script.substringAfter("else {").substringAfter("video[0] = '").substringBefore("'").lowercase()
|
val secondVideo = script.substringAfter("else {").substringAfter("video[0] = '").substringBefore("'").lowercase()
|
||||||
|
|
||||||
when {
|
when {
|
||||||
firstVideo.contains("streamtape") -> StreamTapeExtractor(client).videoFromUrl(firstVideo, "StreamTape")?.let { videoList.add(it) }
|
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 {
|
when {
|
||||||
secondVideo.contains("streamtape") -> StreamTapeExtractor(client).videoFromUrl(secondVideo, "StreamTape")?.let { videoList.add(it) }
|
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
|
return videoList
|
||||||
}
|
}
|
||||||
@ -120,9 +131,14 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||||
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||||
val typeFilter = filterList.find { it is TypeFilter } as TypeFilter
|
val typeFilter = filterList.find { it is TypeFilter } as TypeFilter
|
||||||
|
val typeSearch = when (typeFilter.toUriPart()) {
|
||||||
|
"anime" -> "vostfr"
|
||||||
|
"anime-vf" -> "vf"
|
||||||
|
else -> "vostfr"
|
||||||
|
}
|
||||||
|
|
||||||
return when {
|
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) {
|
typeFilter.state != 0 || query.isNotBlank() -> when (page) {
|
||||||
1 -> GET("$baseUrl/${typeFilter.toUriPart()}")
|
1 -> GET("$baseUrl/${typeFilter.toUriPart()}")
|
||||||
else -> GET("$baseUrl/${typeFilter.toUriPart()}/$page")
|
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<List<SearchJson>>(response.asJsoup().body().text())
|
||||||
|
val animes = mutableListOf<SAnime>()
|
||||||
|
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 {
|
override fun animeDetailsParse(document: Document): SAnime {
|
||||||
val anime = SAnime.create()
|
val anime = SAnime.create()
|
||||||
anime.title = document.selectFirst("div.col.offset-lg-3.offset-md-4 h1").text()
|
anime.title = document.selectFirst("div.col.offset-lg-3.offset-md-4 h1").text()
|
||||||
anime.description = document.select("div.synopsis p").text()
|
anime.description = document.select("div.synopsis p").text()
|
||||||
anime.thumbnail_url = document.select("div.cover img").attr("src")
|
anime.thumbnail_url = document.select("div.cover img").attr("src")
|
||||||
val a = document.select("div.cover img")
|
|
||||||
|
|
||||||
return anime
|
return anime
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,8 +244,8 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
screen.addPreference(videoQualityPref)
|
screen.addPreference(videoQualityPref)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun PstreamExtractor(url: String): Video {
|
private fun pstreamExtractor(url: String): Video {
|
||||||
val noVideo = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4"
|
val noVideo = "http://discloud-storage.herokuapp.com/file/cf781d7d4d02a84b85620ed9ddf7066b/amogus.mp4"
|
||||||
val document = Jsoup.connect(url).headers(
|
val document = Jsoup.connect(url).headers(
|
||||||
mapOf(
|
mapOf(
|
||||||
"Accept" to "*/*",
|
"Accept" to "*/*",
|
||||||
@ -207,7 +255,6 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
)
|
)
|
||||||
).get()
|
).get()
|
||||||
document.select("script").forEach { Script ->
|
document.select("script").forEach { Script ->
|
||||||
|
|
||||||
if (Script.attr("src").contains("https://www.pstream.net/u/player-script")) {
|
if (Script.attr("src").contains("https://www.pstream.net/u/player-script")) {
|
||||||
val playerScript = Jsoup.connect(Script.attr("src")).headers(
|
val playerScript = Jsoup.connect(Script.attr("src")).headers(
|
||||||
mapOf(
|
mapOf(
|
||||||
@ -237,6 +284,36 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
}
|
}
|
||||||
return Video(noVideo, "NO VIDEO", noVideo)
|
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<String> = arrayListOf(),
|
||||||
|
var url_image: String? = null,
|
||||||
|
var score: String? = null,
|
||||||
|
var startDateYear: String? = null,
|
||||||
|
var nbEps: String? = null
|
||||||
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Reference in New Issue
Block a user