diff --git a/src/all/netfilm/AndroidManifest.xml b/src/all/netfilm/AndroidManifest.xml
deleted file mode 100644
index 568741e54..000000000
--- a/src/all/netfilm/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/src/all/netfilm/build.gradle b/src/all/netfilm/build.gradle
deleted file mode 100644
index aeb08c94b..000000000
--- a/src/all/netfilm/build.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlinx-serialization'
-
-ext {
- extName = 'NetFilm'
- pkgNameSuffix = 'all.netfilm'
- extClass = '.NetFilm'
- extVersionCode = 2
- libVersion = '13'
-}
-
-apply from: "$rootDir/common.gradle"
diff --git a/src/all/netfilm/res/mipmap-hdpi/ic_launcher.png b/src/all/netfilm/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index a067097ec..000000000
Binary files a/src/all/netfilm/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/netfilm/res/mipmap-mdpi/ic_launcher.png b/src/all/netfilm/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 4cd35acc3..000000000
Binary files a/src/all/netfilm/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/netfilm/res/mipmap-xhdpi/ic_launcher.png b/src/all/netfilm/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index d4c3bf663..000000000
Binary files a/src/all/netfilm/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/netfilm/res/mipmap-xxhdpi/ic_launcher.png b/src/all/netfilm/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index c9475ad6d..000000000
Binary files a/src/all/netfilm/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/netfilm/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/netfilm/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 95c63c38f..000000000
Binary files a/src/all/netfilm/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/netfilm/res/web_hi_res_512.png b/src/all/netfilm/res/web_hi_res_512.png
deleted file mode 100644
index 436a59fcf..000000000
Binary files a/src/all/netfilm/res/web_hi_res_512.png and /dev/null differ
diff --git a/src/all/netfilm/src/eu/kanade/tachiyomi/animeextension/all/netfilm/DataModel.kt b/src/all/netfilm/src/eu/kanade/tachiyomi/animeextension/all/netfilm/DataModel.kt
deleted file mode 100644
index 2ddf33ff2..000000000
--- a/src/all/netfilm/src/eu/kanade/tachiyomi/animeextension/all/netfilm/DataModel.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package eu.kanade.tachiyomi.animeextension.all.netfilm
-
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class CategoryResponse(
- val data: List,
-) {
- @Serializable
- data class CategoryData(
- val coverVerticalUrl: String,
- val domainType: Int,
- val id: String,
- val name: String,
- val sort: String,
- )
-}
-
-@Serializable
-data class AnimeInfoResponse(
- val data: InfoData,
-) {
- @Serializable
- data class InfoData(
- val coverVerticalUrl: String,
- val episodeVo: List,
- val id: String,
- val introduction: String,
- val name: String,
- val category: Int,
- val tagList: List,
- ) {
- @Serializable
- data class EpisodeInfo(
- val id: Int,
- val seriesNo: Float,
- )
-
- @Serializable
- data class IdInfo(
- val name: String,
- )
- }
-}
-
-@Serializable
-data class SearchResponse(
- val data: InfoData,
-) {
- @Serializable
- data class InfoData(
- val results: List,
- )
-}
-
-@Serializable
-data class EpisodeResponse(
- val data: EpisodeData,
-) {
- @Serializable
- data class EpisodeData(
- val qualities: List,
- val subtitles: List,
- ) {
- @Serializable
- data class Quality(
- val quality: Int,
- val url: String,
- )
-
- @Serializable
- data class Subtitle(
- val language: String,
- val url: String,
- )
- }
-}
-
-@Serializable
-data class LinkData(
- val category: String,
- val id: String,
- val url: String,
- val episodeId: String? = null,
-)
diff --git a/src/all/netfilm/src/eu/kanade/tachiyomi/animeextension/all/netfilm/NetFilm.kt b/src/all/netfilm/src/eu/kanade/tachiyomi/animeextension/all/netfilm/NetFilm.kt
deleted file mode 100644
index 31d2dd0e9..000000000
--- a/src/all/netfilm/src/eu/kanade/tachiyomi/animeextension/all/netfilm/NetFilm.kt
+++ /dev/null
@@ -1,279 +0,0 @@
-package eu.kanade.tachiyomi.animeextension.all.netfilm
-
-import android.app.Application
-import android.content.SharedPreferences
-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.Track
-import eu.kanade.tachiyomi.animesource.model.Video
-import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
-import eu.kanade.tachiyomi.network.GET
-import kotlinx.serialization.decodeFromString
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
-import okhttp3.HttpUrl.Companion.toHttpUrl
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.Response
-import rx.Observable
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import uy.kohesive.injekt.injectLazy
-import kotlin.math.ceil
-import kotlin.math.floor
-
-class NetFilm : ConfigurableAnimeSource, AnimeHttpSource() {
-
- override val name = "NetFilm"
-
- override val baseUrl = "https://net-film.vercel.app/api"
-
- private val hostName = baseUrl.toHttpUrl().host
-
- override val lang = "all"
-
- private var sort = ""
-
- override val supportsLatest = true
-
- private val json: Json by injectLazy()
-
- private val preferences: SharedPreferences by lazy {
- Injekt.get().getSharedPreferences("source_$id", 0x0000)
- }
-
- override val client: OkHttpClient = network.cloudflareClient
-
- private val newHeaders = headers.newBuilder()
- .add("Accept", "application/json, text/plain, */*")
- .add("appid", "eyJhbGciOiJIUzI1NiJ9")
- .add("Host", hostName)
- .add("Sec-Fetch-Dest", "empty")
- .add("Sec-Fetch-Mode", "cors")
- .add("Sec-Fetch-Site", "same-origin")
-
- // ============================== Popular ===============================
-
- override fun popularAnimeRequest(page: Int): Request {
- if (page == 1) sort = ""
- val popHeaders = newHeaders.add("Referer", "https://$hostName/explore").build()
- return GET("$baseUrl/category?area=&category=1&order=count¶ms=COMIC&size=30&sort=$sort&subtitles=&year=", headers = popHeaders)
- }
-
- override fun popularAnimeParse(response: Response): AnimesPage {
- val parsed = json.decodeFromString(response.body.string())
- if (parsed.data.isEmpty()) {
- return AnimesPage(emptyList(), false)
- }
-
- val animeList = parsed.data.map { ani ->
- SAnime.create().apply {
- title = ani.name
- thumbnail_url = ani.coverVerticalUrl
- setUrlWithoutDomain(
- LinkData(
- ani.domainType.toString(),
- ani.id,
- response.request.url.toString(),
- ).toJsonString(),
- )
- }
- }
-
- sort = parsed.data.last().sort
-
- return AnimesPage(animeList, animeList.size == 30)
- }
-
- // =============================== Latest ===============================
-
- override fun latestUpdatesRequest(page: Int): Request {
- if (page == 1) sort = ""
- val latestHeaders = newHeaders.add("Referer", "https://$hostName/explore").build()
- return GET("$baseUrl/category?area=&category=1&order=up¶ms=COMIC&size=30&sort=$sort&subtitles=&year=", headers = latestHeaders)
- }
-
- override fun latestUpdatesParse(response: Response): AnimesPage = popularAnimeParse(response)
-
- // =============================== Search ===============================
-
- override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
- if (page == 1) sort = ""
- return if (query.isNotEmpty()) {
- val searchHeaders = newHeaders.add("Referer", "$baseUrl/search?keyword=$query&size=30").build()
- GET("$baseUrl/search?keyword=$query&size=30", headers = searchHeaders)
- } else {
- val pageList = filters.find { it is SubPageFilter } as SubPageFilter
- val pageHeaders = newHeaders.add("Referer", "https://$hostName/explore").build()
- GET("$baseUrl${pageList.toUriPart()}&sort=$sort&subtitles=&year=", headers = pageHeaders)
- }
- }
-
- override fun searchAnimeParse(response: Response): AnimesPage {
- val url = response.request.url.encodedPath
- return if (url.startsWith("/api/category")) {
- popularAnimeParse(response)
- } else {
- val parsed = json.decodeFromString(response.body.string())
- if (parsed.data.results.isEmpty()) {
- return AnimesPage(emptyList(), false)
- }
-
- val animeList = parsed.data.results.map { ani ->
- SAnime.create().apply {
- title = ani.name
- thumbnail_url = ani.coverVerticalUrl
- setUrlWithoutDomain(
- LinkData(
- ani.domainType.toString(),
- ani.id,
- response.request.url.toString(),
- ).toJsonString(),
- )
- }
- }
-
- sort = parsed.data.results.last().sort
-
- AnimesPage(animeList, animeList.size == 30)
- }
- }
-
- // ============================== FILTERS ===============================
-
- override fun getFilterList(): AnimeFilterList = AnimeFilterList(
- AnimeFilter.Header("Text search ignores filters"),
- SubPageFilter(),
- )
-
- private class SubPageFilter : UriPartFilter(
- "Sub Page",
- arrayOf(
- Pair("Popular Movie", "/category?area=&category=1&order=count¶ms=MOVIE,TVSPECIAL&size=30"),
- Pair("Recent Movie", "/category?area=&category=1&order=up¶ms=MOVIE,TVSPECIAL&size=30"),
- Pair("Popular TV Series", "/category?area=&category=1&order=count¶ms=TV,SETI,MINISERIES,VARIETY,TALK,DOCUMENTARY&size=30"),
- Pair("Recent TV Series", "/category?area=&category=1&order=up¶ms=TV,SETI,MINISERIES,VARIETY,TALK,DOCUMENTARY&size=30"),
- Pair("Popular Anime", "/category?area=&category=1&order=count¶ms=COMIC&size=30"),
- Pair("Recent Anime", "/category?area=&category=1&order=up¶ms=COMIC&size=30"),
- ),
- )
-
- private open class UriPartFilter(displayName: String, val vals: Array>) :
- AnimeFilter.Select(displayName, vals.map { it.first }.toTypedArray()) {
- fun toUriPart() = vals[state].second
- }
-
- // =========================== Anime Details ============================
-
- override fun fetchAnimeDetails(anime: SAnime): Observable {
- val parsed = json.decodeFromString(anime.url)
- val detailsHeader = newHeaders.add("Referer", parsed.url).build()
-
- val resp = client.newCall(
- GET("$baseUrl/detail?category=${parsed.category}&id=${parsed.id}", headers = detailsHeader),
- ).execute()
- val data = json.decodeFromString(resp.body.string()).data
- return Observable.just(
- anime.apply {
- title = data.name
- thumbnail_url = data.coverVerticalUrl
- description = data.introduction
- genre = data.tagList.joinToString(", ") { it.name }
- },
- )
- }
-
- override fun animeDetailsParse(response: Response): SAnime = throw Exception("Not used")
-
- // ============================== Episodes ==============================
-
- override fun fetchEpisodeList(anime: SAnime): Observable> {
- val parsed = json.decodeFromString(anime.url)
-
- val episodeHeader = newHeaders.add("Referer", parsed.url).build()
- val resp = client.newCall(
- GET("$baseUrl/detail?category=${parsed.category}&id=${parsed.id}", headers = episodeHeader),
- ).execute()
- val data = json.decodeFromString(resp.body.string()).data
- val episodeList = data.episodeVo.map { ep ->
- val formattedEpNum = if (floor(ep.seriesNo) == ceil(ep.seriesNo)) {
- ep.seriesNo.toInt()
- } else {
- ep.seriesNo
- }
- SEpisode.create().apply {
- episode_number = ep.seriesNo
- setUrlWithoutDomain(
- LinkData(
- data.category.toString(),
- data.id,
- "$baseUrl/detail?category=${parsed.category}&id=${parsed.id}",
- ep.id.toString(),
- ).toJsonString(),
- )
- name = "Episode $formattedEpNum"
- }
- }
- return Observable.just(episodeList.reversed())
- }
-
- override fun episodeListParse(response: Response): List = throw Exception("Not used")
-
- // ============================ Video Links =============================
-
- override fun fetchVideoList(episode: SEpisode): Observable> {
- val parsed = json.decodeFromString(episode.url)
- val videoHeaders = newHeaders.add("Referer", parsed.url).build()
- val resp = client.newCall(
- GET("$baseUrl/episode?category=${parsed.category}&id=${parsed.id}&episode=${parsed.episodeId!!}", headers = videoHeaders),
- ).execute()
- val episodeParsed = json.decodeFromString(resp.body.string())
- val subtitleList = episodeParsed.data.subtitles.map { sub ->
- Track(sub.url, sub.language)
- }
- val videoList = episodeParsed.data.qualities.map { quality ->
- Video(quality.url, "${quality.quality}p", quality.url, subtitleTracks = subtitleList)
- }
- return Observable.just(videoList.sort())
- }
-
- // ============================= Utilities ==============================
-
- private fun LinkData.toJsonString(): String {
- return json.encodeToString(this)
- }
-
- override fun List