@@ -145,16 +145,21 @@ class AnimeVerseProvider : MainAPI() {
145145 if (queryWords.isEmpty()) return emptyList()
146146
147147 return items.filter { item ->
148- val title = item.title.lowercase()
149- val altTitle = item.alternativeTitle?.lowercase() ? : " "
148+ val title = item.title.lowercase()
149+ val altTitle = item.alternativeTitle?.lowercase() ? : " "
150150 val searchTitle = item.searchTitle?.lowercase() ? : " "
151151 queryWords.all { word ->
152152 title.contains(word) || altTitle.contains(word) || searchTitle.contains(word)
153153 }
154154 }.map { item ->
155- val isDub = item.slug.contains(" -dub" , ignoreCase = true ) ||
156- item.title.contains(" (dub)" , ignoreCase = true )
157- newAnimeSearchResponse(item.title, item.slug) {
155+ val isDub = item.slug.contains(" -dub" , ignoreCase = true ) ||
156+ item.title.contains(" (dub)" , ignoreCase = true )
157+ // Map content type to correct Cloudstream TvType for proper UI category icons
158+ val tvType = when (item.type?.uppercase()) {
159+ " MOVIE" -> TvType .AnimeMovie
160+ else -> TvType .Anime
161+ }
162+ newAnimeSearchResponse(item.title, item.slug, tvType) {
158163 this .posterUrl = if (item.cover.startsWith(" /i/" )) " $mainUrl${item.cover} " else item.cover
159164 this .dubStatus = if (isDub) EnumSet .of(DubStatus .Dubbed ) else EnumSet .of(DubStatus .Subbed )
160165 }
@@ -165,11 +170,14 @@ class AnimeVerseProvider : MainAPI() {
165170 // just-added is the fastest + most reliable endpoint (~1s), load it first
166171 Pair (" just-added" , " Just Added" ),
167172 Pair (" recent" , " Recent Episodes" ),
168- Pair (" trending" , " Trending This Week" )
173+ Pair (" trending" , " Trending This Week" ),
174+ Pair (" movies" , " Anime Movies" ),
175+ Pair (" ova" , " OVA & Specials" )
169176 )
170177
171178 override suspend fun getMainPage (page : Int , request : MainPageRequest ): HomePageResponse {
172179 var hasMore = false
180+ val pageSize = 20
173181
174182 val animeList: List <SearchResponse > = when (request.data) {
175183 " just-added" -> {
@@ -184,11 +192,11 @@ class AnimeVerseProvider : MainAPI() {
184192 }
185193 }
186194 " recent" -> {
187- // No pagination — single page of 10 latest episode updates
195+ // No pagination — single page of latest episode updates
188196 val jsonText = signedGet(" /api/v1/recent" )
189197 val items = mapper.readValue(jsonText, RecentResponse ::class .java).items
190198 items.map { item ->
191- newAnimeSearchResponse(" ${item.seriesTitle} - Episode ${item.number} " , item.seriesSlug) {
199+ newAnimeSearchResponse(" ${item.seriesTitle} · Ep ${item.number} " , item.seriesSlug) {
192200 this .posterUrl = getPosterUrl(item.seriesId)
193201 }
194202 }
@@ -203,6 +211,31 @@ class AnimeVerseProvider : MainAPI() {
203211 }
204212 }
205213 }
214+ " movies" -> {
215+ // Filter catalog locally by type == Movie; paginate with pageSize items per page
216+ val all = getCatalog().filter { it.type?.uppercase() == " MOVIE" }
217+ val start = (page - 1 ) * pageSize
218+ val slice = if (start < all.size) all.subList(start, minOf(start + pageSize, all.size)) else emptyList()
219+ hasMore = (start + pageSize) < all.size
220+ slice.map { item ->
221+ newAnimeSearchResponse(item.title, item.slug, TvType .AnimeMovie ) {
222+ this .posterUrl = if (item.cover.startsWith(" /i/" )) " $mainUrl${item.cover} " else item.cover
223+ }
224+ }
225+ }
226+ " ova" -> {
227+ // Filter catalog locally by type == OVA, ONA, Special, TV Special
228+ val ovaTypes = setOf (" OVA" , " ONA" , " SPECIAL" , " TV SPECIAL" )
229+ val all = getCatalog().filter { it.type?.uppercase() in ovaTypes }
230+ val start = (page - 1 ) * pageSize
231+ val slice = if (start < all.size) all.subList(start, minOf(start + pageSize, all.size)) else emptyList()
232+ hasMore = (start + pageSize) < all.size
233+ slice.map { item ->
234+ newAnimeSearchResponse(item.title, item.slug, TvType .Anime ) {
235+ this .posterUrl = if (item.cover.startsWith(" /i/" )) " $mainUrl${item.cover} " else item.cover
236+ }
237+ }
238+ }
206239 else -> emptyList()
207240 }
208241
@@ -232,14 +265,20 @@ class AnimeVerseProvider : MainAPI() {
232265 }
233266 }.reversed()
234267
268+ // The detail endpoint omits genres/studios/year — look them up from the catalog cache
269+ val catalogMeta = getCatalog().find { it.slug == slug }
270+ val genreTags = catalogMeta?.genres ? : emptyList()
271+ val studioTags = catalogMeta?.studios?.map { " Studio: $it " } ? : emptyList()
272+ val yearTag = catalogMeta?.year?.let { listOf (" Year: $it " ) } ? : emptyList()
273+ val typeTag = catalogMeta?.type?.let { listOf (it) } ? : emptyList()
274+ val allTags = (typeTag + genreTags + studioTags + yearTag).distinct()
275+
235276 return newAnimeLoadResponse(details.title, url, tvType) {
236- this .posterUrl = if (details.cover.startsWith(" /i/" )) " $mainUrl${details.cover} " else details.cover
237- this .backgroundPosterUrl = details.thumb?.let {
238- if (it.startsWith(" /i/" )) " $mainUrl$it " else it
239- }
240- this .plot = details.synopsis
241- this .tags = details.genres
242- this .episodes = mutableMapOf (dubStatus to episodes)
277+ this .posterUrl = if (details.cover.startsWith(" /i/" )) " $mainUrl${details.cover} " else details.cover
278+ this .backgroundPosterUrl = details.thumb?.let { if (it.startsWith(" /i/" )) " $mainUrl$it " else it }
279+ this .plot = details.synopsis
280+ this .tags = allTags.ifEmpty { null }
281+ this .episodes = mutableMapOf (dubStatus to episodes)
243282 }
244283 }
245284
0 commit comments