chore(all/netflixmirror): Remove Netflix Mirror (#2272)

This commit is contained in:
AwkwardPeak7
2023-09-28 22:43:39 +05:00
committed by GitHub
parent 6ba513a4d8
commit c1829105e6
15 changed files with 0 additions and 472 deletions

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@ -1,18 +0,0 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
}
ext {
extName = 'NetFlix Mirror'
pkgNameSuffix = 'all.netflixmirror'
extClass = '.NetFlixMirror'
extVersionCode = 1
}
dependencies {
implementation(project(':lib-playlist-utils'))
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

View File

@ -1,45 +0,0 @@
package eu.kanade.tachiyomi.animeextension.all.netflixmirror
import android.util.Log
import android.webkit.CookieManager
import okhttp3.Interceptor
import okhttp3.Response
class CookieInterceptor(
private val domain: String,
private val key: String,
private val value: String,
) : Interceptor {
init {
val url = "https://$domain/"
val cookie = "$key=$value; Domain=$domain; Path=/"
setCookie(url, cookie)
}
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
if (!request.url.host.endsWith(domain)) return chain.proceed(request)
val cookie = "$key=$value"
val cookieList = request.header("Cookie")?.split("; ") ?: emptyList()
if (cookie in cookieList) return chain.proceed(request)
setCookie("https://$domain/", "$cookie; Domain=$domain; Path=/")
val prefix = "$key="
val newCookie = buildList(cookieList.size + 1) {
cookieList.filterNotTo(this) { it.startsWith(prefix) }
add(cookie)
}.joinToString("; ")
val newRequest = request.newBuilder().header("Cookie", newCookie).build()
return chain.proceed(newRequest)
}
private fun setCookie(url: String, value: String) {
try {
CookieManager.getInstance().setCookie(url, value)
} catch (e: Exception) {
Log.e(domain, "failed to set cookie", e)
}
}
}

View File

@ -1,271 +0,0 @@
package eu.kanade.tachiyomi.animeextension.all.netflixmirror
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.all.netflixmirror.dto.DetailsDto
import eu.kanade.tachiyomi.animeextension.all.netflixmirror.dto.EpisodeUrl
import eu.kanade.tachiyomi.animeextension.all.netflixmirror.dto.EpisodesDto
import eu.kanade.tachiyomi.animeextension.all.netflixmirror.dto.SearchDto
import eu.kanade.tachiyomi.animeextension.all.netflixmirror.dto.SeasonEpisodesDto
import eu.kanade.tachiyomi.animeextension.all.netflixmirror.dto.VideoList
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
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.AnimeHttpSource
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Request
import okhttp3.Response
import org.jsoup.select.Elements
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import kotlin.math.min
class NetFlixMirror : AnimeHttpSource(), ConfigurableAnimeSource {
override val name = "NetFlix Mirror"
override val baseUrl = "https://m.netflixmirror.com"
override val lang = "all"
override val supportsLatest = false
private val json: Json by injectLazy()
override val client = network.cloudflareClient.newBuilder()
.addNetworkInterceptor(
CookieInterceptor(baseUrl.toHttpUrl().host, "hd", "on"),
)
.build()
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
private val xhrHeaders by lazy {
headersBuilder()
.add("X-Requested-With", "XMLHttpRequest")
.build()
}
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val playListUtils by lazy {
PlaylistUtils(client, headers)
}
private lateinit var pageElements: Elements
override fun fetchPopularAnime(page: Int): Observable<AnimesPage> {
return if (page == 1) {
super.fetchPopularAnime(page)
} else {
Observable.just(paginatedAnimePageParse(page))
}
}
override fun popularAnimeRequest(page: Int): Request {
return GET("$baseUrl/home", headers)
}
override fun popularAnimeParse(response: Response): AnimesPage {
pageElements = response.asJsoup().select("article > a.post-data")
return paginatedAnimePageParse(1)
}
private fun paginatedAnimePageParse(page: Int): AnimesPage {
val end = min(page * 20, pageElements.size)
val entries = pageElements.subList((page - 1) * 20, end).map {
SAnime.create().apply {
title = "" // no title here
url = it.attr("data-post")
thumbnail_url = it.selectFirst("img")?.attr("abs:data-src")
}
}
return AnimesPage(entries, end < pageElements.size)
}
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.isNotEmpty()) {
super.fetchSearchAnime(page, query, filters)
} else {
if (page == 1) {
val pageFilter = filters.filterIsInstance<PageFilter>().firstOrNull()?.selected ?: "/home"
val request = GET(baseUrl + pageFilter, headers)
client.newCall(request)
.asObservableSuccess()
.map(::popularAnimeParse)
} else {
Observable.just(paginatedAnimePageParse(page))
}
}
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val url = "$baseUrl/search.php".toHttpUrl().newBuilder().apply {
addQueryParameter("s", query.trim())
addQueryParameter("t", System.currentTimeMillis().toString())
}.build().toString()
return GET(url, xhrHeaders)
}
override fun getFilterList() = getFilters()
override fun searchAnimeParse(response: Response): AnimesPage {
val result = response.parseAs<SearchDto>()
val entries = result.searchResult?.map {
SAnime.create().apply {
url = it.id
title = it.title
thumbnail_url = idToThumbnailUrl(it.id)
}
} ?: emptyList()
return AnimesPage(entries, false)
}
override fun animeDetailsRequest(anime: SAnime): Request {
val url = "$baseUrl/post.php?id=${anime.url}&t=${System.currentTimeMillis()}"
return GET(url, xhrHeaders)
}
override fun animeDetailsParse(response: Response): SAnime {
val result = response.parseAs<DetailsDto>()
val id = response.request.url.queryParameter("id")!!
return SAnime.create().apply {
title = result.title
url = id
thumbnail_url = idToThumbnailUrl(id)
genre = "${result.genre}, ${result.cast}"
author = result.creator
artist = result.director
description = result.desc
if (!result.lang.isNullOrEmpty()) {
description += "\n\nAvailable Language(s): ${result.lang.joinToString { it.language }}"
}
status = result.status
}
}
override fun episodeListRequest(anime: SAnime) = animeDetailsRequest(anime)
override fun episodeListParse(response: Response): List<SEpisode> {
val result = response.parseAs<EpisodesDto>()
val id = response.request.url.queryParameter("id")!!
if (result.episodes?.firstOrNull() == null) {
return SEpisode.create().apply {
name = "Movie"
url = EpisodeUrl(id, result.title).let(json::encodeToString)
}.let(::listOf)
}
val episodes = result.episodes.mapNotNull {
if (it == null) return@mapNotNull null
it.toSEpisode(result.title)
}.toMutableList()
result.season?.reversed()?.drop(1)?.forEach { season ->
val seasonRequest = GET("$baseUrl/episodes.php?s=${season.id}&series=$id&t=${System.currentTimeMillis()}", xhrHeaders)
val seasonResponse = client.newCall(seasonRequest).execute().parseAs<SeasonEpisodesDto>()
episodes.addAll(
index = 0,
elements = seasonResponse.episodes?.map {
it.toSEpisode(result.title)
} ?: emptyList(),
)
}
return episodes.reversed()
}
override fun videoListRequest(episode: SEpisode): Request {
val episodeUrl = episode.url.parseAs<EpisodeUrl>()
val url = "$baseUrl/playlist.php".toHttpUrl().newBuilder().apply {
addQueryParameter("id", episodeUrl.id)
addQueryParameter("t", episodeUrl.title)
addQueryParameter("tm", System.currentTimeMillis().toString())
}.build().toString()
return GET(url, xhrHeaders)
}
override fun videoListParse(response: Response): List<Video> {
val result = response.parseAs<VideoList>()
val masterPlayList = result
.firstOrNull()
?.sources
?.firstOrNull()
?.file
?.let { baseUrl + it }
?.toHttpUrlOrNull()
?.newBuilder()
?.removeAllQueryParameters("q")
?.build()
?.toString()
?: return emptyList()
return playListUtils.extractFromHls(masterPlayList)
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY, PREF_QUALITY_DEFAULT)!!
return this.sortedWith(
compareBy { it.quality.contains(quality) },
).reversed()
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_QUALITY
title = PREF_QUALITY_TITLE
entries = arrayOf("720p", "480p", "360p")
entryValues = arrayOf("720", "480", "360")
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
}.also(screen::addPreference)
}
private inline fun <reified T> String.parseAs(): T =
json.decodeFromString(this)
private inline fun <reified T> Response.parseAs(): T =
use { it.body.string() }.parseAs()
companion object {
private const val PREF_QUALITY = "preferred_quality"
private const val PREF_QUALITY_TITLE = "Preferred quality"
private const val PREF_QUALITY_DEFAULT = "720"
private fun idToThumbnailUrl(id: String) = "https://img.netflixmirror.com/poster/v/$id.jpg"
}
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used")
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Not used")
}

View File

@ -1,28 +0,0 @@
package eu.kanade.tachiyomi.animeextension.all.netflixmirror
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
abstract class SelectFilter(
name: String,
private val options: List<Pair<String, String>>,
) : AnimeFilter.Select<String>(
name,
options.map { it.first }.toTypedArray(),
) {
val selected get() = options[state].second.takeUnless { it.isEmpty() }
}
class PageFilter : SelectFilter(
"Page",
listOf(
Pair("Home", ""),
Pair("Movies", "/movies"),
Pair("Series", "/series"),
),
)
fun getFilters() = AnimeFilterList(
PageFilter(),
AnimeFilter.Separator("Doesn't work with text search."),
)

View File

@ -1,28 +0,0 @@
package eu.kanade.tachiyomi.animeextension.all.netflixmirror.dto
import eu.kanade.tachiyomi.animesource.model.SAnime
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class DetailsDto(
val title: String,
val genre: String,
val cast: String,
val desc: String,
val creator: String,
val director: String,
val episodes: List<Episode?>? = emptyList(),
val lang: List<LanguageDto>? = emptyList(),
) {
val status = if (episodes?.firstOrNull() == null) {
SAnime.COMPLETED
} else {
SAnime.UNKNOWN
}
}
@Serializable
data class LanguageDto(
@SerialName("l") val language: String,
)

View File

@ -1,50 +0,0 @@
package eu.kanade.tachiyomi.animeextension.all.netflixmirror.dto
import eu.kanade.tachiyomi.animesource.model.SEpisode
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy
@Serializable
data class EpisodesDto(
val title: String,
val season: List<Season>? = emptyList(),
val episodes: List<Episode?>? = emptyList(),
)
@Serializable
data class Season(
val id: String,
)
@Serializable
data class SeasonEpisodesDto(
val episodes: List<Episode>? = emptyList(),
)
@Serializable
data class Episode(
val id: String,
@SerialName("t") val title: String,
@SerialName("s") val season: String,
@SerialName("ep") val episode: String,
val time: String,
) {
fun toSEpisode(seriesTitle: String) = SEpisode.create().apply {
name = "$season $episode: $title"
url = EpisodeUrl(id, seriesTitle).let(JSON::encodeToString)
scanlator = time
}
companion object {
private val JSON: Json by injectLazy()
}
}
@Serializable
data class EpisodeUrl(
val id: String,
val title: String,
)

View File

@ -1,15 +0,0 @@
package eu.kanade.tachiyomi.animeextension.all.netflixmirror.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class SearchDto(
val searchResult: List<SearchResult>? = emptyList(),
)
@Serializable
data class SearchResult(
val id: String,
@SerialName("t") val title: String,
)

View File

@ -1,15 +0,0 @@
package eu.kanade.tachiyomi.animeextension.all.netflixmirror.dto
import kotlinx.serialization.Serializable
typealias VideoList = List<VideoDto>
@Serializable
data class VideoDto(
val sources: List<PlayList>,
)
@Serializable
data class PlayList(
val file: String,
)