diff --git a/src/en/seez/AndroidManifest.xml b/src/en/seez/AndroidManifest.xml new file mode 100644 index 000000000..568741e54 --- /dev/null +++ b/src/en/seez/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/en/seez/build.gradle b/src/en/seez/build.gradle new file mode 100644 index 000000000..fd896a58a --- /dev/null +++ b/src/en/seez/build.gradle @@ -0,0 +1,22 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) +} + +ext { + extName = 'Seez' + pkgNameSuffix = 'en.seez' + extClass = '.Seez' + extVersionCode = 1 + libVersion = '13' +} + +dependencies { + implementation(project(':lib-filemoon-extractor')) + implementation(project(':lib-streamtape-extractor')) + implementation(project(':lib-playlist-utils')) +} + + +apply from: "$rootDir/common.gradle" diff --git a/src/en/seez/res/mipmap-hdpi/ic_launcher.png b/src/en/seez/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..41ef17c34 Binary files /dev/null and b/src/en/seez/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/seez/res/mipmap-mdpi/ic_launcher.png b/src/en/seez/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..402737887 Binary files /dev/null and b/src/en/seez/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/seez/res/mipmap-xhdpi/ic_launcher.png b/src/en/seez/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..6bf2ef21e Binary files /dev/null and b/src/en/seez/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/seez/res/mipmap-xxhdpi/ic_launcher.png b/src/en/seez/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..272e5ed38 Binary files /dev/null and b/src/en/seez/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/seez/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/seez/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..8f181f6b6 Binary files /dev/null and b/src/en/seez/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/seez/res/web_hi_res_512.png b/src/en/seez/res/web_hi_res_512.png new file mode 100644 index 000000000..548973187 Binary files /dev/null and b/src/en/seez/res/web_hi_res_512.png differ diff --git a/src/en/seez/src/eu/kanade/tachiyomi/animeextension/en/seez/Seez.kt b/src/en/seez/src/eu/kanade/tachiyomi/animeextension/en/seez/Seez.kt new file mode 100644 index 000000000..f1b34dde7 --- /dev/null +++ b/src/en/seez/src/eu/kanade/tachiyomi/animeextension/en/seez/Seez.kt @@ -0,0 +1,415 @@ +package eu.kanade.tachiyomi.animeextension.en.seez + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animeextension.en.seez.extractors.VidsrcExtractor +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.AnimeHttpSource +import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Locale + +class Seez : ConfigurableAnimeSource, AnimeHttpSource() { + + override val name = "Seez" + + override val baseUrl = "https://seez.su" + + private val embedUrl = "https://vidsrc.to" + + override val lang = "en" + + override val supportsLatest = false + + override val client: OkHttpClient = network.cloudflareClient + + private val json: Json by injectLazy() + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + private val vrfHelper by lazy { VrfHelper(client, headers) } + + private val apiKey by lazy { + val jsUrl = client.newCall(GET(baseUrl, headers)).execute().asJsoup() + .select("script[defer][src]")[1].attr("abs:src") + + val jsBody = client.newCall(GET(jsUrl, headers)).execute().use { it.body.string() } + Regex("""f="(\w{20,})"""").find(jsBody)!!.groupValues[1] + } + + private val apiHeaders = headers.newBuilder().apply { + add("Accept", "application/json, text/javascript, */*; q=0.01") + add("Host", "api.themoviedb.org") + add("Origin", baseUrl) + add("Referer", "$baseUrl/") + }.build() + + // ============================== Popular =============================== + + override fun popularAnimeRequest(page: Int): Request { + val url = TMDB_URL.newBuilder().apply { + addPathSegment("movie") + addPathSegment("popular") + addQueryParameter("language", "en-US") + addQueryParameter("page", page.toString()) + }.buildAPIUrl() + + return GET(url, headers = apiHeaders) + } + + override fun popularAnimeParse(response: Response): AnimesPage { + val data = response.parseAs() + + val animeList = data.results.map { ani -> + val name = ani.title ?: ani.name ?: "Title N/A" + + SAnime.create().apply { + title = name + url = LinkData(ani.id, "movie").toJsonString() + thumbnail_url = ani.poster_path?.let { IMG_URL + it } ?: FALLBACK_IMG + } + } + + return AnimesPage(animeList, data.page < data.total_pages) + } + + // =============================== Latest =============================== + + override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") + + override fun latestUpdatesParse(response: Response): AnimesPage = throw Exception("Not used") + + // =============================== 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 collectionFilter = filterList.find { it is CollectionFilter } as CollectionFilter + val orderFilter = filterList.find { it is OrderFilter } as OrderFilter + + val url = if (query.isNotBlank()) { + TMDB_URL.newBuilder().apply { + addPathSegment("search") + addPathSegment("multi") + addQueryParameter("query", query) + addQueryParameter("page", page.toString()) + }.buildAPIUrl() + } else { + TMDB_URL.newBuilder().apply { + addPathSegment(typeFilter.toUriPart()) + addPathSegment(orderFilter.toUriPart()) + if (collectionFilter.state != 0) { + addQueryParameter("with_networks", collectionFilter.toUriPart()) + } + addQueryParameter("language", "en-US") + addQueryParameter("page", page.toString()) + }.buildAPIUrl() + } + + return GET(url, headers = apiHeaders) + } + + override fun searchAnimeParse(response: Response): AnimesPage { + val data = response.parseAs() + + val animeList = data.results.map { ani -> + val name = ani.title ?: ani.name ?: "Title N/A" + + SAnime.create().apply { + title = name + url = LinkData(ani.id, ani.media_type).toJsonString() + thumbnail_url = ani.poster_path?.let { IMG_URL + it } ?: FALLBACK_IMG + } + } + + return AnimesPage(animeList, data.page < data.total_pages) + } + + // ============================== Filters =============================== + + override fun getFilterList(): AnimeFilterList = AnimeFilterList( + AnimeFilter.Header("NOTE: Filters are going to be ignored if using search text"), + TypeFilter(), + CollectionFilter(), + OrderFilter(), + ) + + private class TypeFilter : UriPartFilter( + "Type", + arrayOf( + Pair("Movies", "movie"), + Pair("TV-shows", "tv"), + ), + ) + + private class CollectionFilter : UriPartFilter( + "Collection", + arrayOf( + Pair("