Fix neko sama + FRAnime (#128)

* FrAnime Fix

* NekoSama Fix

* Update build.gradle

* Update NekoSama.kt

* Update FrAnimeDto.kt

* Update FrAnimeDto.kt
This commit is contained in:
mobby45 2024-08-09 16:03:01 +02:00 committed by GitHub
parent ae5466af0c
commit cc963b11ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 100 additions and 33 deletions

View file

@ -0,0 +1,7 @@
plugins {
id("lib-android")
}
dependencies {
implementation(project(":lib:playlist-utils"))
}

View file

@ -0,0 +1,40 @@
package eu.kanade.tachiyomi.lib.vidmolyextractor
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.internal.EMPTY_HEADERS
class VidMolyExtractor(private val client: OkHttpClient, headers: Headers = EMPTY_HEADERS) {
private val baseUrl = "https://vidmoly.to"
private val playlistUtils by lazy { PlaylistUtils(client) }
private val headers: Headers = headers.newBuilder()
.set("Origin", baseUrl)
.set("Referer", "$baseUrl/")
.build()
private val sourcesRegex = Regex("sources: (.*?]),")
private val urlsRegex = Regex("""file:"(.*?)"""")
fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
val document = client.newCall(
GET(url, headers.newBuilder().set("Sec-Fetch-Dest", "iframe").build())
).execute().asJsoup()
val script = document.selectFirst("script:containsData(sources)")!!.data()
val sources = sourcesRegex.find(script)!!.groupValues[1]
val urls = urlsRegex.findAll(sources).map { it.groupValues[1] }.toList()
return urls.flatMap {
playlistUtils.extractFromHls(it,
videoNameGen = { quality -> "${prefix}VidMoly - $quality" },
masterHeaders = headers,
videoHeaders = headers,
)
}
}
}

View file

@ -11,4 +11,5 @@ dependencies {
implementation(project(':lib:vk-extractor')) implementation(project(':lib:vk-extractor'))
implementation(project(':lib:sendvid-extractor')) implementation(project(':lib:sendvid-extractor'))
implementation(project(':lib:sibnet-extractor')) implementation(project(':lib:sibnet-extractor'))
implementation project(':lib:vidmoly-extractor')
} }

View file

@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.sendvidextractor.SendvidExtractor import eu.kanade.tachiyomi.lib.sendvidextractor.SendvidExtractor
import eu.kanade.tachiyomi.lib.sibnetextractor.SibnetExtractor import eu.kanade.tachiyomi.lib.sibnetextractor.SibnetExtractor
import eu.kanade.tachiyomi.lib.vidmolyextractor.VidMolyExtractor
import eu.kanade.tachiyomi.lib.vkextractor.VkExtractor import eu.kanade.tachiyomi.lib.vkextractor.VkExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
@ -101,7 +102,7 @@ class FrAnime : AnimeHttpSource() {
SEpisode.create().apply { SEpisode.create().apply {
setUrlWithoutDomain(anime.url + "&ep=${index + 1}") setUrlWithoutDomain(anime.url + "&ep=${index + 1}")
name = episode.title name = episode.title ?: "Episode ${index + 1}"
episode_number = (index + 1).toFloat() episode_number = (index + 1).toFloat()
} }
} }
@ -123,14 +124,19 @@ class FrAnime : AnimeHttpSource() {
val players = if (episodeLang == "vo") episodeData.languages.vo.players else episodeData.languages.vf.players val players = if (episodeLang == "vo") episodeData.languages.vo.players else episodeData.languages.vf.players
val sendvidExtractor by lazy { SendvidExtractor(client, headers) }
val sibnetExtractor by lazy { SibnetExtractor(client) }
val vkExtractor by lazy { VkExtractor(client, headers) }
val vidMolyExtractor by lazy { VidMolyExtractor(client) }
val videos = players.withIndex().parallelCatchingFlatMap { (index, playerName) -> val videos = players.withIndex().parallelCatchingFlatMap { (index, playerName) ->
val apiUrl = "$videoBaseUrl/$episodeLang/$index" val apiUrl = "$videoBaseUrl/$episodeLang/$index"
val playerUrl = client.newCall(GET(apiUrl, headers)).await().body.string() val playerUrl = client.newCall(GET(apiUrl, headers)).await().body.string()
when (playerName) { when (playerName) {
"vido" -> listOf(Video(playerUrl, "FRAnime (Vido)", playerUrl)) "sendvid" -> sendvidExtractor.videosFromUrl(playerUrl)
"sendvid" -> SendvidExtractor(client, headers).videosFromUrl(playerUrl) "sibnet" -> sibnetExtractor.videosFromUrl(playerUrl)
"sibnet" -> SibnetExtractor(client).videosFromUrl(playerUrl) "vk" -> vkExtractor.videosFromUrl(playerUrl)
"vk" -> VkExtractor(client, headers).videosFromUrl(playerUrl) "vidmoly" -> vidMolyExtractor.videosFromUrl(playerUrl)
else -> emptyList() else -> emptyList()
} }
} }

View file

@ -20,7 +20,6 @@ typealias BigIntegerJson =
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
private object BigIntegerSerializer : KSerializer<BigInteger> { private object BigIntegerSerializer : KSerializer<BigInteger> {
override val descriptor = PrimitiveSerialDescriptor("java.math.BigInteger", PrimitiveKind.LONG) override val descriptor = PrimitiveSerialDescriptor("java.math.BigInteger", PrimitiveKind.LONG)
override fun deserialize(decoder: Decoder): BigInteger = override fun deserialize(decoder: Decoder): BigInteger =
@ -68,7 +67,7 @@ data class Season(
@Serializable @Serializable
data class Episode( data class Episode(
@SerialName("title") val title: String = "!No Title!", @SerialName("title") val title: String?,
@SerialName("lang") val languages: EpisodeLanguages, @SerialName("lang") val languages: EpisodeLanguages,
) )

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'NekoSama' extName = 'NekoSama'
extClass = '.NekoSama' extClass = '.NekoSama'
extVersionCode = 11 extVersionCode = 12
isNsfw = true isNsfw = true
} }

View file

@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parseAs import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import okhttp3.HttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -28,7 +29,14 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Neko-Sama" override val name = "Neko-Sama"
override val baseUrl by lazy { "https://" + preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! } override val baseUrl by lazy {
val domain = preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!!
HttpUrl.Builder()
.scheme("https")
.host(domain)
.build()
.toString()
}
override val lang = "fr" override val lang = "fr"
@ -72,17 +80,19 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
else -> "vostfr" else -> "vostfr"
} }
return when { val url = when {
query.isNotBlank() -> GET("$baseUrl/animes-search-$typeSearch.json?$query") query.isNotBlank() -> "$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 -> "$baseUrl/${typeFilter.toUriPart()}"
else -> GET("$baseUrl/${typeFilter.toUriPart()}/$page") else -> "$baseUrl/${typeFilter.toUriPart()}/page$page"
} }
else -> when (page) { else -> when (page) {
1 -> GET("$baseUrl/anime/") 1 -> "$baseUrl/anime/"
else -> GET("$baseUrl/anime/page/$page") else -> "$baseUrl/anime/page$page"
} }
} }
return GET(url)
} }
override fun searchAnimeParse(response: Response): AnimesPage { override fun searchAnimeParse(response: Response): AnimesPage {
@ -95,11 +105,13 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val animes = jsonSearch val animes = jsonSearch
.filter { it.title.orEmpty().lowercase().contains(query) } .filter { it.title.orEmpty().lowercase().contains(query) }
.mapNotNull { .mapNotNull {
SAnime.create().apply { val anime = SAnime.create().apply {
url = it.url ?: return@mapNotNull null url = it.url?.substringAfterLast("/")?.substringBefore("-") ?: return@mapNotNull null
title = it.title ?: return@mapNotNull null title = it.title ?: return@mapNotNull null
thumbnail_url = it.url_image ?: "$baseUrl/images/default_poster.png" thumbnail_url = it.url_image ?: "$baseUrl/images/default_poster.png"
setUrlWithoutDomain(url) // call setUrlWithoutDomain on the SAnime instance
} }
anime
} }
AnimesPage(animes, false) AnimesPage(animes, false)
} }
@ -131,9 +143,10 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
SAnime.create().apply { SAnime.create().apply {
val itemUrl = item.url ?: return@mapNotNull null val itemUrl = item.url ?: return@mapNotNull null
title = item.title ?: return@mapNotNull null title = item.title ?: return@mapNotNull null
val type = itemUrl.substringAfterLast("-") val animeId = itemUrl.substringAfterLast("/").substringBefore("-")
url = itemUrl.replace("episode", "info").substringBeforeLast("-").substringBeforeLast("-") + "-$type" val titleSlug = title.replace("[^a-zA-Z0-9 -]".toRegex(), "").replace(" ", "-").lowercase()
thumbnail_url = item.url_image ?: "$baseUrl/images/default_poster.png" url = "anime/info/$animeId-${titleSlug}_vostfr"
thumbnail_url = item.url_image ?: "/images/default_poster.png"
} }
} }
@ -179,8 +192,9 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeFromElement(element: Element) = SEpisode.create().apply { override fun episodeFromElement(element: Element) = SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
val text = element.text() val text = element.text()
name = text.substringBeforeLast(" - ") val episodeNumber = text.substringAfterLast("- ").toFloatOrNull() ?: 0F
episode_number = text.substringAfterLast("- ").toFloatOrNull() ?: 0F name = "Épisode ${episodeNumber.toInt()}"
episode_number = episodeNumber
} }
// ============================ Video Links ============================= // ============================ Video Links =============================
@ -291,8 +305,8 @@ class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private val PLAYERS_REGEX = Regex("video\\s*\\[\\d*]\\s*=\\s*'(.*?)'") private val PLAYERS_REGEX = Regex("video\\s*\\[\\d*]\\s*=\\s*'(.*?)'")
private const val PREF_DOMAIN_KEY = "pref_domain_key" private const val PREF_DOMAIN_KEY = "pref_domain_key"
private const val PREF_DOMAIN_TITLE = "Preferred domain" private const val PREF_DOMAIN_TITLE = "Preferred domain"
private const val PREF_DOMAIN_DEFAULT = "animecat.net"
private val PREF_DOMAIN_ENTRIES = arrayOf("animecat.net", "neko-sama.fr") private val PREF_DOMAIN_ENTRIES = arrayOf("animecat.net", "neko-sama.fr")
private const val PREF_DOMAIN_DEFAULT = "animecat.net"
private const val PREF_QUALITY_KEY = "preferred_quality" private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_TITLE = "Preferred quality" private const val PREF_QUALITY_TITLE = "Preferred quality"