diff --git a/src/fr/nekosama/build.gradle b/src/fr/nekosama/build.gradle deleted file mode 100644 index 6b230c85..00000000 --- a/src/fr/nekosama/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -ext { - extName = 'NekoSama' - extClass = '.NekoSama' - extVersionCode = 12 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" - -dependencies { - implementation(project(':lib:streamtape-extractor')) - implementation(project(':lib:fusevideo-extractor')) -} diff --git a/src/fr/nekosama/res/mipmap-hdpi/ic_launcher.png b/src/fr/nekosama/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 236b668e..00000000 Binary files a/src/fr/nekosama/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/nekosama/res/mipmap-mdpi/ic_launcher.png b/src/fr/nekosama/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 236b668e..00000000 Binary files a/src/fr/nekosama/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/nekosama/res/mipmap-xhdpi/ic_launcher.png b/src/fr/nekosama/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 236b668e..00000000 Binary files a/src/fr/nekosama/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/nekosama/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/nekosama/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 236b668e..00000000 Binary files a/src/fr/nekosama/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/nekosama/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/nekosama/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 236b668e..00000000 Binary files a/src/fr/nekosama/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ 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 deleted file mode 100644 index 2bf76ce1..00000000 --- a/src/fr/nekosama/src/eu/kanade/tachiyomi/animeextension/fr/nekosama/NekoSama.kt +++ /dev/null @@ -1,316 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.fr.nekosama - -import android.app.Application -import androidx.preference.ListPreference -import androidx.preference.PreferenceScreen -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.lib.fusevideoextractor.FusevideoExtractor -import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.util.asJsoup -import eu.kanade.tachiyomi.util.parseAs -import kotlinx.serialization.Serializable -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class NekoSama : ConfigurableAnimeSource, ParsedAnimeHttpSource() { - - override val name = "Neko-Sama" - - 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 supportsLatest = true - - private val preferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - // ============================== Popular =============================== - override fun popularAnimeSelector() = "div.anime" - - override fun popularAnimeRequest(page: Int): Request { - return if (page > 1) { - GET("$baseUrl/anime/$page") - } else { - GET("$baseUrl/anime/") - } - } - - override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { - with(element.selectFirst("div.info a")!!) { - setUrlWithoutDomain(attr("href")) - title = text() - } - - element.selectFirst("div.cover a div img:not(.placeholder)")?.run { - thumbnail_url = attr("data-src").ifBlank { attr("src") } - } - } - - override fun popularAnimeNextPageSelector() = "div.nekosama.pagination a.active ~ a" - - // =============================== Search =============================== - 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" - } - - val url = when { - query.isNotBlank() -> "$baseUrl/animes-search-$typeSearch.json?$query" - typeFilter.state != 0 || query.isNotBlank() -> when (page) { - 1 -> "$baseUrl/${typeFilter.toUriPart()}" - else -> "$baseUrl/${typeFilter.toUriPart()}/page$page" - } - else -> when (page) { - 1 -> "$baseUrl/anime/" - else -> "$baseUrl/anime/page$page" - } - } - - return GET(url) - } - - override fun searchAnimeParse(response: Response): AnimesPage { - val pageUrl = response.request.url.toString() - val query = pageUrl.substringAfter("?").lowercase().replace("%20", " ") - - return when { - pageUrl.contains("animes-search") -> { - val jsonSearch = response.parseAs<List<SearchJson>>() - val animes = jsonSearch - .filter { it.title.orEmpty().lowercase().contains(query) } - .mapNotNull { - val anime = SAnime.create().apply { - url = it.url?.substringAfterLast("/")?.substringBefore("-") ?: return@mapNotNull null - title = it.title ?: return@mapNotNull null - thumbnail_url = it.url_image ?: "$baseUrl/images/default_poster.png" - setUrlWithoutDomain(url) // call setUrlWithoutDomain on the SAnime instance - } - anime - } - AnimesPage(animes, false) - } - else -> { - val page = response.asJsoup() - val animes = page.select(popularAnimeSelector()).map(::popularAnimeFromElement) - AnimesPage(animes, true) - } - } - } - - override fun searchAnimeFromElement(element: Element): SAnime = throw UnsupportedOperationException() - - override fun searchAnimeNextPageSelector(): String = throw UnsupportedOperationException() - - override fun searchAnimeSelector(): String = throw UnsupportedOperationException() - - // =============================== Latest =============================== - override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers) - - override fun latestUpdatesSelector() = throw UnsupportedOperationException() - - override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException() - - override fun latestUpdatesParse(response: Response): AnimesPage { - val jsonLatest = response.parseAs<List<SearchJson>> { it.substringAfter("var lastEpisodes = ").substringBefore(";\n") } - - val animeList = jsonLatest.mapNotNull { item -> - SAnime.create().apply { - val itemUrl = item.url ?: return@mapNotNull null - title = item.title ?: return@mapNotNull null - val animeId = itemUrl.substringAfterLast("/").substringBefore("-") - val titleSlug = title.replace("[^a-zA-Z0-9 -]".toRegex(), "").replace(" ", "-").lowercase() - url = "anime/info/$animeId-${titleSlug}_vostfr" - thumbnail_url = item.url_image ?: "/images/default_poster.png" - } - } - - return AnimesPage(animeList, false) - } - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException() - - // =========================== Anime Details ============================ - override fun animeDetailsParse(document: Document) = SAnime.create().apply { - title = document.selectFirst("div.row > div.col > h1")!!.ownText() - genre = document.select("div.col > div.list span").joinToString { it.text() } - - with(document.selectFirst("div.row > div#details")!!) { - thumbnail_url = selectFirst("div.cover img")?.absUrl("src") ?: "$baseUrl/images/default_poster.png" - description = buildString { - document.selectFirst("div.synopsis p")?.also { append(it.text(), "\n\n") } - getInfo("Score")?.also { append("Score moyen: *", it, "\n") } - getInfo("Status")?.also { - append("Status: ", it, "\n") - status = parseStatus(it) - } - getInfo("Format")?.also { append("Format: ", it, "\n") } - getInfo("Diffusion")?.also { append("Diffusion: ", it, "\n") } - } - } - } - - private fun Element.getInfo(item: String) = - selectFirst("div#anime-info-list div.item:contains($item)")?.ownText()?.trim() - - private fun parseStatus(statusString: String): Int { - return when (statusString) { - "En cours" -> SAnime.ONGOING - "Terminé" -> SAnime.COMPLETED - else -> SAnime.UNKNOWN - } - } - - // ============================== Episodes ============================== - override fun episodeListSelector() = "div.episodes div > a.button" - - override fun episodeFromElement(element: Element) = SEpisode.create().apply { - setUrlWithoutDomain(element.attr("href")) - val text = element.text() - val episodeNumber = text.substringAfterLast("- ").toFloatOrNull() ?: 0F - name = "Épisode ${episodeNumber.toInt()}" - episode_number = episodeNumber - } - - // ============================ Video Links ============================= - private val fusevideoExtractor by lazy { FusevideoExtractor(client, headers) } - private val streamTapeExtractor by lazy { StreamTapeExtractor(client) } - - override fun videoListParse(response: Response): List<Video> { - val document = response.asJsoup() - val script = document.selectFirst("script:containsData(var video = [];)")!!.data() - return PLAYERS_REGEX.findAll(script).flatMap { - val url = it.groupValues[1] - with(url) { - when { - contains("fusevideo") -> fusevideoExtractor.videosFromUrl(this) - contains("streamtape") -> streamTapeExtractor.videosFromUrl(this) - else -> emptyList() - } - } - }.toList() - } - - override fun videoListSelector() = throw UnsupportedOperationException() - - override fun videoUrlParse(document: Document) = throw UnsupportedOperationException() - - override fun videoFromElement(element: Element) = throw UnsupportedOperationException() - - override fun List<Video>.sort(): List<Video> { - val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! - - return sortedWith( - compareBy { it.quality.contains(quality) }, - ).reversed() - } - - // ============================== Filters =============================== - override fun getFilterList(): AnimeFilterList = AnimeFilterList( - AnimeFilter.Header("Utilisez ce filtre pour affiner votre recherche"), - TypeFilter(), - ) - - private class TypeFilter : UriPartFilter( - "VOSTFR or VF", - arrayOf( - Pair("<sélectionner>", "none"), - Pair("VOSTFR", "anime"), - Pair("VF", "anime-vf"), - ), - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - // ============================== Settings ============================== - override fun setupPreferenceScreen(screen: PreferenceScreen) { - ListPreference(screen.context).apply { - key = PREF_QUALITY_KEY - title = PREF_QUALITY_TITLE - entries = PREF_QUALITY_ENTRIES - entryValues = PREF_QUALITY_ENTRIES - setDefaultValue(PREF_QUALITY_DEFAULT) - summary = "%s" - }.also(screen::addPreference) - - ListPreference(screen.context).apply { - key = PREF_DOMAIN_KEY - title = PREF_DOMAIN_TITLE - entries = PREF_DOMAIN_ENTRIES - entryValues = PREF_DOMAIN_ENTRIES - setDefaultValue(PREF_DOMAIN_DEFAULT) - summary = "%s" - }.also(screen::addPreference) - } - - @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, - - ) - - companion object { - private val PLAYERS_REGEX = Regex("video\\s*\\[\\d*]\\s*=\\s*'(.*?)'") - private const val PREF_DOMAIN_KEY = "pref_domain_key" - private const val PREF_DOMAIN_TITLE = "Preferred domain" - 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_TITLE = "Preferred quality" - private const val PREF_QUALITY_DEFAULT = "1080p" - private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p") - } -}