diff --git a/src/en/uniquestream/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/dooplay/uniquestream/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/en/uniquestream/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/dooplay/uniquestream/res/mipmap-hdpi/ic_launcher.png diff --git a/src/en/uniquestream/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/dooplay/uniquestream/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/en/uniquestream/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/dooplay/uniquestream/res/mipmap-mdpi/ic_launcher.png diff --git a/src/en/uniquestream/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/dooplay/uniquestream/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/en/uniquestream/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/dooplay/uniquestream/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/en/uniquestream/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/dooplay/uniquestream/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/en/uniquestream/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/dooplay/uniquestream/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/en/uniquestream/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/dooplay/uniquestream/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/en/uniquestream/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/dooplay/uniquestream/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/en/uniquestream/res/web_hi_res_512.png b/multisrc/overrides/dooplay/uniquestream/res/web_hi_res_512.png similarity index 100% rename from src/en/uniquestream/res/web_hi_res_512.png rename to multisrc/overrides/dooplay/uniquestream/res/web_hi_res_512.png diff --git a/multisrc/overrides/dooplay/uniquestream/src/UniqueStream.kt b/multisrc/overrides/dooplay/uniquestream/src/UniqueStream.kt new file mode 100644 index 000000000..908695544 --- /dev/null +++ b/multisrc/overrides/dooplay/uniquestream/src/UniqueStream.kt @@ -0,0 +1,321 @@ +package eu.kanade.tachiyomi.animeextension.en.uniquestream + +import eu.kanade.tachiyomi.animesource.model.AnimeFilter +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +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.multisrc.dooplay.DooPlay +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import org.jsoup.nodes.Element +import rx.Observable +import uy.kohesive.injekt.injectLazy + +class UniqueStream : DooPlay( + "en", + "UniqueStream", + "https://uniquestream.net", +) { + override val client = network.cloudflareClient + + private val json: Json by injectLazy() + + // ============================== Popular =============================== + + override fun popularAnimeRequest(page: Int) = GET("$baseUrl/ratings/${page.toPage()}") + + override fun popularAnimeSelector() = latestUpdatesSelector() + + override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector() + + // =============================== Latest =============================== + + override fun latestUpdatesNextPageSelector() = "div.pagination > *:last-child:not(span):not(.current)" + + // =============================== Search =============================== + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val cleanQuery = query.replace(" ", "+").lowercase() + + val filterList = if (filters.isEmpty()) getFilterList() else filters + val genreFilter = filterList.find { it is GenreFilter } as GenreFilter + val recentFilter = filterList.find { it is RecentFilter } as RecentFilter + val yearFilter = filterList.find { it is YearFilter } as YearFilter + + return when { + query.isNotBlank() -> GET("$baseUrl/${page.toPage()}?s=$cleanQuery", headers = headers) + genreFilter.state != 0 -> GET("$baseUrl/genre/${genreFilter.toUriPart()}/${page.toPage()}/", headers = headers) + recentFilter.state != 0 -> GET("$baseUrl/${recentFilter.toUriPart()}/${page.toPage()}", headers = headers) + yearFilter.state != 0 -> GET("$baseUrl/release/${yearFilter.toUriPart()}/${page.toPage()}", headers = headers) + else -> popularAnimeRequest(page) + } + } + + // ============================== Filters =============================== + + override fun getFilterList(): AnimeFilterList = AnimeFilterList( + AnimeFilter.Header("Text search ignores filters"), + GenreFilter(), + RecentFilter(), + YearFilter(), + ) + + private class GenreFilter : UriPartFilter( + "Genres", + arrayOf( + Pair("", ""), + Pair("Recent TV Shows", "tvshows"), + Pair("Recent Movies", "movies"), + ), + ) + + private class YearFilter : UriPartFilter( + "Release Year", + arrayOf( + Pair("", ""), - Pair("Action", "action"), - Pair("Action & Adventure", "action-adventure"), - Pair("Adventure", "adventure"), - Pair("Animation", "animation"), - Pair("Anime", "anime"), - Pair("Asian", "asian"), - Pair("Bollywood", "bollywood"), - Pair("Comedy", "comedy"), - Pair("Crime", "crime"), - Pair("Documentary", "documentary"), - Pair("Drama", "drama"), - Pair("Family", "family"), - Pair("Fantasy", "fantasy"), - Pair("Foreign", "foreign"), - Pair("History", "history"), - Pair("Hollywood", "hollywood"), - Pair("Horror", "horror"), - Pair("Kids", "kids"), - Pair("Korean", "korean"), - Pair("Malay", "malay"), - Pair("Malayalam", "malayalam"), - Pair("Military", "military"), - Pair("Music", "music"), - Pair("Mystery", "mystery"), - Pair("News", "news"), - Pair("Reality", "reality"), - Pair("Romance", "romance"), - Pair("Sci-Fi & Fantasy", "sci-fi-fantasy"), - Pair("Science Fiction", "science-fiction"), - Pair("Soap", "soap"), - Pair("Talk", "talk"), - Pair("Tamil", "tamil"), - Pair("Telugu", "telugu"), - Pair("Thriller", "thriller"), - Pair("TV Movie", "tv-movie"), - Pair("War", "war"), - Pair("War & Politics", "war-politics"), - Pair("Western", "western"), - ), - ) - - private class RecentFilter : UriPartFilter( - "Recent", - arrayOf( - Pair("", ""), - Pair("2023", "2023"), - Pair("2022", "2022"), - Pair("2021", "2021"), - Pair("2020", "2020"), - Pair("2019", "2019"), - Pair("2018", "2018"), - Pair("2017", "2017"), - Pair("2016", "2016"), - Pair("2015", "2015"), - Pair("2014", "2014"), - Pair("2013", "2013"), - Pair("2012", "2012"), - Pair("2011", "2011"), - Pair("2010", "2010"), - Pair("2009", "2009"), - Pair("2008", "2008"), - Pair("2007", "2007"), - Pair("2006", "2006"), - Pair("2005", "2005"), - Pair("2004", "2004"), - Pair("2003", "2003"), - Pair("2002", "2002"), - Pair("2001", "2001"), - Pair("2000", "2000"), - Pair("1999", "1999"), - Pair("1998", "1998"), - Pair("1997", "1997"), - Pair("1996", "1996"), - Pair("1995", "1995"), - Pair("1994", "1994"), - Pair("1993", "1993"), - Pair("1992", "1992"), - Pair("1991", "1991"), - Pair("1990", "1990"), - Pair("1989", "1989"), - Pair("1988", "1988"), - Pair("1987", "1987"), - Pair("1986", "1986"), - Pair("1985", "1985"), - Pair("1984", "1984"), - Pair("1983", "1983"), - Pair("1982", "1982"), - Pair("1981", "1981"), - Pair("1980", "1980"), - Pair("1979", "1979"), - Pair("1978", "1978"), - Pair("1977", "1977"), - Pair("1976", "1976"), - Pair("1975", "1975"), - Pair("1974", "1974"), - ), - ) - - private open class UriPartFilter(displayName: String, val vals: Array>) : - AnimeFilter.Select(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private class URLFilter : AnimeFilter.Text("Url") - - // =========================== Anime Details ============================ - - override fun animeDetailsParse(document: Document): SAnime { - return SAnime.create().apply { - title = document.selectFirst("div.data > h1")!!.text() - thumbnail_url = if (document.selectFirst("div.poster > img")!!.hasAttr("data-wpfc-original-src")) { - document.selectFirst("div.poster > img")!!.attr("data-wpfc-original-src") - } else { - document.selectFirst("div.poster > img")!!.attr("src") - } - status = SAnime.COMPLETED - description = document.selectFirst("div:contains(Synopsis) > div > p")?.text() ?: "" - } - } - - // ============================== Episodes ============================== - - override fun episodeListParse(response: Response): List { - val document = response.asJsoup() - val episodeList = mutableListOf() - - if (response.request.url.encodedPath.startsWith("/movies/")) { - val episode = SEpisode.create() - episode.name = document.selectFirst("div.data > h1")!!.text().replace(":", "") - episode.episode_number = 1F - episode.setUrlWithoutDomain(response.request.url.encodedPath) - episodeList.add(episode) - } else { - var counter = 1 - for (season in document.select("div#seasons > div")) { - val seasonList = mutableListOf() - for (ep in season.select("ul > li")) { - if (ep.childrenSize() > 0) { - val episode = SEpisode.create() - - episode.name = "Season ${ep.selectFirst("div.numerando")!!.ownText()} - ${ep.selectFirst("a[href]")!!.ownText()}" - episode.episode_number = counter.toFloat() - episode.setUrlWithoutDomain(ep.selectFirst("a[href]")!!.attr("href").toHttpUrl().encodedPath) - - seasonList.add(episode) - counter++ - } - } - episodeList.addAll(seasonList) - } - } - - return episodeList.reversed() - } - - override fun episodeListSelector(): String = throw Exception("Not Used") - - override fun episodeFromElement(element: Element): SEpisode = throw Exception("Not Used") - - // ============================ Video Links ============================= - - override fun fetchVideoList(episode: SEpisode): Observable> { - val videoList = mutableListOf