diff --git a/AnimeONProvider/build.gradle.kts b/AnimeONProvider/build.gradle.kts new file mode 100644 index 0000000..1b6607c --- /dev/null +++ b/AnimeONProvider/build.gradle.kts @@ -0,0 +1,30 @@ +// use an integer for version numbers +version = 1 + +dependencies{ + implementation("com.google.code.gson:gson:2.9.0") +} + +cloudstream { + language = "uk" + // All of these properties are optional, you can safely remove them + + description = "AnimeON - Дивитися аніме україньскою" + authors = listOf("CakesTwix") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "Anime", + "AnimeMovie", + "OVA", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=animeon.club&sz=%size%" +} \ No newline at end of file diff --git a/AnimeONProvider/src/main/AndroidManifest.xml b/AnimeONProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/AnimeONProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/AnimeONProvider/src/main/kotlin/com/lagradost/AnimeONProvider.kt b/AnimeONProvider/src/main/kotlin/com/lagradost/AnimeONProvider.kt new file mode 100644 index 0000000..2b8801a --- /dev/null +++ b/AnimeONProvider/src/main/kotlin/com/lagradost/AnimeONProvider.kt @@ -0,0 +1,248 @@ +package com.lagradost + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.lagradost.cloudstream3.DubStatus +import com.lagradost.cloudstream3.Episode +import com.lagradost.cloudstream3.HomePageResponse +import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.MainAPI +import com.lagradost.cloudstream3.MainPageRequest +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.ShowStatus +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.addDubStatus +import com.lagradost.cloudstream3.addEpisodes +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.mainPageOf +import com.lagradost.cloudstream3.newAnimeLoadResponse +import com.lagradost.cloudstream3.newAnimeSearchResponse +import com.lagradost.cloudstream3.newHomePageResponse +import com.lagradost.cloudstream3.newMovieLoadResponse +import com.lagradost.cloudstream3.toRatingInt +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.models.AnimeInfoModel +import com.lagradost.models.AnimeModel +import com.lagradost.models.NewAnimeModel +import com.lagradost.models.SearchModel +import com.lagradost.models.PlayerJson + +class AnimeONProvider : MainAPI() { + + // Basic Info + override var mainUrl = "https://animeon.club" + override var name = "AnimeON" + override val hasMainPage = true + override var lang = "uk" + override val hasDownloadSupport = true + override val supportedTypes = + setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA, + ) + + private val apiUrl = "$mainUrl/api/anime/" + private val posterApi = "$mainUrl/api/uploads/images/%s" + private val searchApi = "$apiUrl/search/%s?full=true" + + // Sections + override val mainPage = + mainPageOf( + "$apiUrl/popular" to "Популярне", + "$apiUrl/seasons" to "Аніме поточного сезону ", + "$apiUrl?pageSize=24&pageIndex=%s&sortType=DESC&sort=created" to "Нове", + ) + + private val listAnimeModel = object : TypeToken>() {}.type + + // Done + override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + if(!request.data.contains("pageIndex") && page !=1) return HomePageResponse(emptyList()) + val document = app.get(request.data.format(page)).text + + // Нове + if(request.data.contains("pageIndex")) { + val parsedJSON = Gson().fromJson(document, NewAnimeModel::class.java) + val homeList = + parsedJSON.results.map { + newAnimeSearchResponse(it.titleUa, "anime/${it.id}", TvType.Anime) { + this.posterUrl = posterApi.format(it.poster) + } + } + // Log.d("CakesTwix-Debug", "$cdnUrl${parsedJSON.data[1].posterId}") + return newHomePageResponse(request.name, homeList) + } else { + val parsedJSON = Gson().fromJson>(document, listAnimeModel) + val homeList = + parsedJSON.map { + newAnimeSearchResponse(it.titleUa, "anime/${it.id}", TvType.Anime) { + this.posterUrl = posterApi.format(it.poster) + } + } + // Log.d("CakesTwix-Debug", "$cdnUrl${parsedJSON.data[1].posterId}") + return newHomePageResponse(request.name, homeList) + } + } + + override suspend fun search(query: String): List { + val animeJSON = + Gson().fromJson(app.get(searchApi.format(query)).text, SearchModel::class.java) + val findList = + animeJSON.result.map { + newAnimeSearchResponse(it.titleUa, "anime/${it.id}", TvType.Anime) { + this.posterUrl = posterApi.format(it.poster) + addDubStatus(isDub = true, it.episodes) + } + } + return findList + } + + // Detailed information + override suspend fun load(url: String): LoadResponse { + val animeJSON = + Gson() + .fromJson( + app.get(url.replace("/anime/", "/api/anime/")).text, + AnimeInfoModel::class.java + ) + + val showStatus = + with(animeJSON.status.name) { + when { + contains("Онґоїнґ") -> ShowStatus.Ongoing + contains("Завершений") -> ShowStatus.Completed + else -> null + } + } + + val tvType = + with(animeJSON.type.name) { + when { + contains("ТБ-Серіал") -> TvType.Anime + contains("OVA") -> TvType.OVA + contains("Спеціальний випуск") -> TvType.OVA + contains("ONA") -> TvType.OVA + contains("Фільм") -> TvType.AnimeMovie + else -> TvType.Anime + } + } + + + val episodes = mutableListOf() + + val playerRawJson = app.get(animeJSON.player.url).document.select("script").html() + .substringAfterLast("file:\'") + .substringBefore("\',") + + tryParseJson>(playerRawJson)?.map { dubs -> // Dubs + for (season in dubs.folder) { // Seasons + for (episode in season.folder) { // Episodes + episodes.add( + Episode( + "${season.title}, ${episode.title}, ${animeJSON.player.url}", + episode.title, + season.title.replace(" Сезон ", "").toIntOrNull(), + episode.title.replace("Серія ", "").toIntOrNull(), + episode.poster + ) + ) + } + } + } + + return if (tvType == TvType.Anime || tvType == TvType.OVA) { + newAnimeLoadResponse( + animeJSON.titleUa, + "$mainUrl/anime/${animeJSON.id}", + tvType, + ) { + this.posterUrl = posterApi.format(animeJSON.poster) + this.engName = animeJSON.titleEn + this.tags = animeJSON.genres.map { it.name } + this.plot = animeJSON.description + addTrailer(animeJSON.trailer) + this.showStatus = showStatus + this.duration = extractIntFromString(animeJSON.episodeTime) + this.year = animeJSON.releaseDate + this.backgroundPosterUrl = posterApi.format(animeJSON.backgroundImage) + this.rating = animeJSON.rating.toString().toRatingInt() + addEpisodes(DubStatus.Dubbed, episodes) + addMalId(animeJSON.malId) + } + } else { + newMovieLoadResponse(animeJSON.titleUa, "$mainUrl/anime/${animeJSON.id}", tvType, "${animeJSON.titleUa}, ${animeJSON.player.url}") { + this.posterUrl = posterApi.format(animeJSON.poster) + this.tags = animeJSON.genres.map { it.name } + this.plot = animeJSON.description + addTrailer(animeJSON.trailer) + this.duration = extractIntFromString(animeJSON.episodeTime) + this.year = animeJSON.releaseDate + this.backgroundPosterUrl = posterApi.format(animeJSON.backgroundImage) + this.rating = animeJSON.rating.toString().toRatingInt() + addMalId(animeJSON.malId) + } + } + } + + + // It works when I click to view the series + override suspend fun loadLinks( + data: String, // (Serisl) [Season, Episode, Player Url] | (Film) [Title, Player Url] + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + val dataList = data.split(", ") + + // Its film, parse one m3u8 + if(dataList.size == 2){ + val m3u8Url = app.get(dataList[1]).document.select("script").html() + .substringAfterLast("file:\"") + .substringBefore("\",") + M3u8Helper.generateM3u8( + source = dataList[0], + streamUrl = m3u8Url, + referer = "https://tortuga.wtf/" + ).forEach(callback) + + return true + } + + val playerRawJson = app.get(dataList[2]).document.select("script").html() + .substringAfterLast("file:\'") + .substringBefore("\',") + + tryParseJson>(playerRawJson)?.map { dubs -> // Dubs + for(season in dubs.folder){ // Seasons + if(season.title == dataList[0]){ + for(episode in season.folder){ // Episodes + if(episode.title == dataList[1]){ + // Add as source + M3u8Helper.generateM3u8( + source = dubs.title, + streamUrl = episode.file, + referer = "https://tortuga.wtf/" + ).forEach(callback) + } + } + } + } + } + return true + } + + private fun extractIntFromString(string: String): Int? { + val value = Regex("(\\d+)").findAll(string).lastOrNull() ?: return null + if (value.value[0].toString() == "0") { + return value.value.drop(1).toIntOrNull() + } + + return value.value.toIntOrNull() + } +} diff --git a/AnimeONProvider/src/main/kotlin/com/lagradost/AnimeONProviderPlugin.kt b/AnimeONProvider/src/main/kotlin/com/lagradost/AnimeONProviderPlugin.kt new file mode 100644 index 0000000..2b09543 --- /dev/null +++ b/AnimeONProvider/src/main/kotlin/com/lagradost/AnimeONProviderPlugin.kt @@ -0,0 +1,13 @@ +package com.lagradost + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class AnimeONProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(AnimeONProvider()) + } +} diff --git a/AnimeONProvider/src/main/kotlin/com/lagradost/models/AnimeInfoModel.kt b/AnimeONProvider/src/main/kotlin/com/lagradost/models/AnimeInfoModel.kt new file mode 100644 index 0000000..93c0953 --- /dev/null +++ b/AnimeONProvider/src/main/kotlin/com/lagradost/models/AnimeInfoModel.kt @@ -0,0 +1,104 @@ +package com.lagradost.models + +import com.google.gson.annotations.SerializedName + +class AnimeInfoModel ( + + @SerializedName("id") val id : Int, + @SerializedName("titleUa") val titleUa : String, + @SerializedName("titleEn") val titleEn : String, + @SerializedName("description") val description : String, + @SerializedName("releaseDate") val releaseDate : Int, + @SerializedName("producer") val producer : String, + @SerializedName("views") val views : Int, + @SerializedName("episodes") val episodes : Int, + @SerializedName("episodeTime") val episodeTime : String, + @SerializedName("poster") val poster : String, + @SerializedName("backgroundImage") val backgroundImage : String, + @SerializedName("episodesAired") val episodesAired : Int, + @SerializedName("createdAt") val createdAt : String, + @SerializedName("updatedAt") val updatedAt : String, + @SerializedName("trailer") val trailer : String, + @SerializedName("ashdiId") val ashdiId : String, + @SerializedName("malId") val malId : Int, + @SerializedName("pl") val pl : String, + @SerializedName("season") val season : Int, + @SerializedName("rating") val rating : Double, + @SerializedName("genres") val genres : List, + @SerializedName("tags") val tags : List, + @SerializedName("studio") val studio : Studio, + @SerializedName("status") val status : Status, + @SerializedName("age") val age : Age, + @SerializedName("fundups") val fundups : List, + @SerializedName("type") val type : Type, + @SerializedName("schedule") val schedule : Schedule, + @SerializedName("franchise") val franchise : Franchise, + @SerializedName("player") val player : Player, + @SerializedName("screenshots") val screenshots : List, + @SerializedName("rank") val rank : Int, + @SerializedName("votes") val votes : Int +) + +data class Schedule ( + + @SerializedName("id") val id : Int, + @SerializedName("date") val name : String, + @SerializedName("episode") val episode : String, +) + +data class Age ( + + @SerializedName("id") val id : Int, + @SerializedName("name") val name : String +) + +data class Franchise ( + + @SerializedName("id") val id : Int, + @SerializedName("weight") val weight : Int +) + +data class Fundups ( + + @SerializedName("id") val id : Int, + @SerializedName("name") val name : String +) + +data class Player ( + + @SerializedName("id") val id : Int, + @SerializedName("url") val url : String, + @SerializedName("numEpisodeFrom") val numEpisodeFrom : String, + @SerializedName("numEpisodeTo") val numEpisodeTo : String +) + +data class Screenshots ( + + @SerializedName("id") val id : Int, + @SerializedName("original") val original : String, + @SerializedName("preview") val preview : String +) + +data class Status ( + + @SerializedName("id") val id : Int, + @SerializedName("name") val name : String +) + +data class Studio ( + + @SerializedName("id") val id : Int, + @SerializedName("name") val name : String +) + +data class Tags ( + + @SerializedName("id") val id : Int, + @SerializedName("name") val name : String +) + +data class Type ( + + @SerializedName("id") val id : Int, + @SerializedName("name") val name : String +) diff --git a/AnimeONProvider/src/main/kotlin/com/lagradost/models/AnimeModel.kt b/AnimeONProvider/src/main/kotlin/com/lagradost/models/AnimeModel.kt new file mode 100644 index 0000000..6d15660 --- /dev/null +++ b/AnimeONProvider/src/main/kotlin/com/lagradost/models/AnimeModel.kt @@ -0,0 +1,20 @@ +package com.lagradost.models + +import com.google.gson.annotations.SerializedName + +class AnimeModel ( + @SerializedName("id") val id : Int, + @SerializedName("titleUa") val titleUa : String, + @SerializedName("description") val description : String, + @SerializedName("poster") val poster : String, + @SerializedName("backgroundImage") val backgroundImage : String, + @SerializedName("rating") val rating : Double, + @SerializedName("genres") val genres : List +) + +data class Genres ( + + @SerializedName("id") val id : Int, + @SerializedName("name") val name : String, + @SerializedName("malId") val malId : Int +) \ No newline at end of file diff --git a/AnimeONProvider/src/main/kotlin/com/lagradost/models/NewAnimeModel.kt b/AnimeONProvider/src/main/kotlin/com/lagradost/models/NewAnimeModel.kt new file mode 100644 index 0000000..a312996 --- /dev/null +++ b/AnimeONProvider/src/main/kotlin/com/lagradost/models/NewAnimeModel.kt @@ -0,0 +1,27 @@ +package com.lagradost.models + +import com.google.gson.annotations.SerializedName + +class NewAnimeModel ( + + @SerializedName("results") val results : List, + @SerializedName("totalCount") val totalCount : Int +) + +data class Results ( + + @SerializedName("id") val id : Int, + @SerializedName("titleUa") val titleUa : String, + @SerializedName("description") val description : String, + @SerializedName("releaseDate") val releaseDate : Int, + @SerializedName("views") val views : Int, + @SerializedName("episodes") val episodes : Int, + @SerializedName("poster") val poster : String, + @SerializedName("episodesAired") val episodesAired : Int, + @SerializedName("createdAt") val createdAt : String, + @SerializedName("malId") val malId : Int, + @SerializedName("rating") val rating : Double, + @SerializedName("status") val status : Status, + @SerializedName("type") val type : Type, + @SerializedName("genres") val genres : List +) \ No newline at end of file diff --git a/AnimeONProvider/src/main/kotlin/com/lagradost/models/PlayerJson.kt b/AnimeONProvider/src/main/kotlin/com/lagradost/models/PlayerJson.kt new file mode 100644 index 0000000..d0d8afe --- /dev/null +++ b/AnimeONProvider/src/main/kotlin/com/lagradost/models/PlayerJson.kt @@ -0,0 +1,22 @@ +package com.lagradost.models + +data class PlayerJson ( + + val title : String, + val folder : List +) + +data class Season ( + + val title : String, + val folder : List +) + +data class Episode ( + + val title : String, + val file : String, + val id : String, + val poster : String, + val subtitle : String, +) diff --git a/AnimeONProvider/src/main/kotlin/com/lagradost/models/SearchModel.kt b/AnimeONProvider/src/main/kotlin/com/lagradost/models/SearchModel.kt new file mode 100644 index 0000000..ad8c389 --- /dev/null +++ b/AnimeONProvider/src/main/kotlin/com/lagradost/models/SearchModel.kt @@ -0,0 +1,33 @@ +package com.lagradost.models + +import com.google.gson.annotations.SerializedName + +class SearchModel ( + + @SerializedName("result") val result : List, + @SerializedName("count") val count : Int +) + +data class Result ( + + @SerializedName("id") val id : Int, + @SerializedName("titleUa") val titleUa : String, + @SerializedName("titleEn") val titleEn : String, + @SerializedName("description") val description : String, + @SerializedName("releaseDate") val releaseDate : Int, + @SerializedName("producer") val producer : String, + @SerializedName("views") val views : Int, + @SerializedName("episodes") val episodes : Int, + @SerializedName("episodeTime") val episodeTime : String, + @SerializedName("poster") val poster : String, + @SerializedName("backgroundImage") val backgroundImage : String, + @SerializedName("episodesAired") val episodesAired : Int, + @SerializedName("createdAt") val createdAt : String, + @SerializedName("updatedAt") val updatedAt : String, + @SerializedName("ashdiId") val ashdiId : String, + @SerializedName("malId") val malId : Int, + @SerializedName("season") val season : Int, + @SerializedName("rating") val rating : Double, + @SerializedName("type") val type : Type, + @SerializedName("status") val status : Status +)