diff --git a/AniageProvider/build.gradle.kts b/AniageProvider/build.gradle.kts new file mode 100644 index 0000000..2f3042f --- /dev/null +++ b/AniageProvider/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 = "Перший нормальний сайт, який зроблено з нуля без використання руснявих технологій. Поки що мало контенту." + 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=aniage.net&sz=%size%" +} \ No newline at end of file diff --git a/AniageProvider/src/main/AndroidManifest.xml b/AniageProvider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29aec9d --- /dev/null +++ b/AniageProvider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/AniageProvider/src/main/kotlin/com/lagradost/AniageProvider.kt b/AniageProvider/src/main/kotlin/com/lagradost/AniageProvider.kt new file mode 100644 index 0000000..1b014c7 --- /dev/null +++ b/AniageProvider/src/main/kotlin/com/lagradost/AniageProvider.kt @@ -0,0 +1,204 @@ +package com.lagradost + +import android.util.Log +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.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.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.models.AnimeDetail +import com.lagradost.models.EpisodesModel +import com.lagradost.models.FindModel +import com.lagradost.models.TeamsModel +import org.json.JSONArray +import org.json.JSONObject + +class AniageProvider : MainAPI() { + + // Basic Info + override var mainUrl = "https://aniage.net" + override var name = "Aniage" + 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 = "https://master.api.aniage.net" + private val cdnUrl = "https://aniage.fra1.cdn.digitaloceanspaces.com/main/" + private val pageSize = 30 + + private val listEpisodeModel = object : TypeToken>() { }.type + private val listTeamsModel = object : TypeToken>() { }.type + + // Sections + override val mainPage = mainPageOf( + mainUrl to "Нове", + ) + + // Done + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + // Log.d("CakesTwix-Debug", page.toString()) + val body = JSONObject() + body.put("cleanup", JSONArray()) + val orderBody = JSONObject() + orderBody.put("by", "lastUpdated") + orderBody.put("direction", "DESC") + + body.put("order", orderBody) + body.put("page", page) + body.put("pageSize", pageSize) + + val document = app.post("$apiUrl/v2/anime/find", + json = body + ).text + val parsedJSON = Gson().fromJson(document, FindModel::class.java) + // Log.d("CakesTwix-Debug", parsedJSON.data[0].title) + + val homeList = parsedJSON.data.map { + newAnimeSearchResponse(it.title, it.id, TvType.Anime) { + this.posterUrl = "$cdnUrl${it.posterId}" + addDubStatus(isDub = true, it.episodes) + this.otherName = it.alternativeTitle + } + } + // Log.d("CakesTwix-Debug", "$cdnUrl${parsedJSON.data[1].posterId}") + return newHomePageResponse(request.name, homeList) + } + + // TODO + override suspend fun search(query: String): List { + + return emptyList() + } + + // Detailed information + override suspend fun load(url: String): LoadResponse { + val animeID = url.replace("$mainUrl/", "") + val document = app.get("$mainUrl/watch/$animeID").document + val jsonObject = JSONObject(document.selectFirst("script[type*=application/json]")!!.html()) + val buildId = jsonObject.getString("buildId") + + // https://www.aniage.net/_next/data/F64n_RAvOkYPvB3Z9Bmw2/watch/fea3c510-f42d-4a18-b438-bfab102f4424.json + // Log.d("CakesTwix-Debug", app.get("$mainUrl/_next/data/$buildId/watch/$animeID.json").text) + val animeJSON = Gson().fromJson(app.get("$mainUrl/_next/data/$buildId/watch/$animeID.json").text, AnimeDetail::class.java) + + // Log.d("CakesTwix-Debug", animeJSON.pageProps.title) + + val showStatus = with(animeJSON.pageProps.titleStatus){ + when{ + contains("Онгоїнг") -> ShowStatus.Ongoing + contains("Вийшло") -> ShowStatus.Completed + else -> null + } + } + + // Episodes + // https://master.api.aniage.net/anime/episodes + // ?animeId=2c60c269-049e-428b-96ba-fae23ac718ec + // &page=1 + // &pageSize=30 + // &sortOrder=ASC + // &teamId=99012182-f177-45df-a21f-6823bb9955c3 + // &volume=1 + + val episodes = mutableListOf() + + // Log.d("CakesTwix-Debug", app.get("https://master.api.aniage.net/anime/episodes?animeId=$animeID&page=1&pageSize=30&sortOrder=ASC&teamId=${teams.teamId}&volume=1").url) + Gson().fromJson>(app.get("https://master.api.aniage.net/anime/episodes?animeId=$animeID&page=1&pageSize=30&sortOrder=ASC&teamId=${animeJSON.pageProps.teams[0].teamId}&volume=1").text, listEpisodeModel).map { + episodes.add(Episode + ( + "${it.animeId}, ${it.episodeNum}", + "Серія ${it.title}", + it.volume, + it.episodeNum, + "$cdnUrl${it.previewPath}", + ) + ) + } + + + return newAnimeLoadResponse( + animeJSON.pageProps.title, + "$mainUrl/watch/$animeID", + TvType.Anime, + ) { + this.posterUrl = "$cdnUrl${animeJSON.pageProps.posterId}" + this.engName = animeJSON.pageProps.alternativeTitle + this.tags = animeJSON.pageProps.genres.map { it } + this.plot = animeJSON.pageProps.description + // addTrailer(animeJSON.pageProps.trailerUrl) + this.showStatus = showStatus + this.duration = animeJSON.pageProps.averageDuration + addEpisodes(DubStatus.Dubbed, episodes) + this.year = extractIntFromString(animeJSON.pageProps.season) + } + } + + // It works when I click to view the series + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + // animeID, Num Episode + val dataList = data.split(", ") + + // Get Episodes List + val document = app.get("$mainUrl/watch/${dataList[0]}").document + val jsonObject = JSONObject(document.selectFirst("script[type*=application/json]")!!.html()) + val buildId = jsonObject.getString("buildId") + + val animeJSON = Gson().fromJson(app.get("$mainUrl/_next/data/$buildId/watch/${dataList[0]}.json").text, AnimeDetail::class.java) + + // Parse list, by episode + animeJSON.pageProps.teams.map { teams -> + val TeamsList = Gson().fromJson>(app.get("$apiUrl/anime/teams/by-ids?ids=${teams.teamId}").text, listTeamsModel)[0] + // Log.d("CakesTwix-Debug", app.get("https://master.api.aniage.net/anime/episodes?animeId=$animeID&page=1&pageSize=30&sortOrder=ASC&teamId=${teams.teamId}&volume=1").url) + Gson().fromJson>(app.get("https://master.api.aniage.net/anime/episodes?animeId=${dataList[0]}&page=1&pageSize=30&sortOrder=ASC&teamId=${teams.teamId}&volume=1").text, listEpisodeModel).map { + if(it.episodeNum == dataList[1].toInt()){ + // Log.d("CakesTwix-Debug", app.get(it.playPath).document.select("source[type*=application/x-mpegURL]").attr("src")) + M3u8Helper.generateM3u8( + source = TeamsList.name, + streamUrl = app.get(it.playPath).document.select("source[type*=application/x-mpegURL]").attr("src"), + referer = mainUrl + ).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() + + } +} \ No newline at end of file diff --git a/AniageProvider/src/main/kotlin/com/lagradost/AniageProviderPlugin.kt b/AniageProvider/src/main/kotlin/com/lagradost/AniageProviderPlugin.kt new file mode 100644 index 0000000..8bcc216 --- /dev/null +++ b/AniageProvider/src/main/kotlin/com/lagradost/AniageProviderPlugin.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 AniageProviderPlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(AniageProvider()) + } +} \ No newline at end of file diff --git a/AniageProvider/src/main/kotlin/com/lagradost/models/AnimeDetailModel.kt b/AniageProvider/src/main/kotlin/com/lagradost/models/AnimeDetailModel.kt new file mode 100644 index 0000000..471bafe --- /dev/null +++ b/AniageProvider/src/main/kotlin/com/lagradost/models/AnimeDetailModel.kt @@ -0,0 +1,38 @@ +package com.lagradost.models + +import com.google.gson.annotations.SerializedName + +data class AnimeDetail ( + @SerializedName("pageProps") val pageProps : PageProps, + @SerializedName("__N_SSP") val __N_SSP : Boolean +) + +data class PageProps ( + + @SerializedName("id") val id : String, + @SerializedName("posterId") val posterId : String, + @SerializedName("title") val title : String, + @SerializedName("alternativeTitle") val alternativeTitle : String, + @SerializedName("type") val type : String, + @SerializedName("titleStatus") val titleStatus : String, + @SerializedName("publishedAt") val publishedAt : String, + @SerializedName("genres") val genres : List, + @SerializedName("description") val description : String, + @SerializedName("season") val season : String, + @SerializedName("studios") val studios : List, + @SerializedName("adult") val adult : Int, + @SerializedName("episodes") val episodes : Int, + @SerializedName("trailerUrl") val trailerUrl : String, + @SerializedName("maxEpisodes") val maxEpisodes : Int, + @SerializedName("averageDuration") val averageDuration : Int, + @SerializedName("teams") val teams : List +) + +data class Teams ( + + @SerializedName("animeId") val animeId : String, + @SerializedName("teamId") val teamId : String, + @SerializedName("episodes") val episodes : Int, + @SerializedName("views") val views : Int, + @SerializedName("lastUpdated") val lastUpdated : String +) \ No newline at end of file diff --git a/AniageProvider/src/main/kotlin/com/lagradost/models/EpisodesModel.kt b/AniageProvider/src/main/kotlin/com/lagradost/models/EpisodesModel.kt new file mode 100644 index 0000000..667a4f7 --- /dev/null +++ b/AniageProvider/src/main/kotlin/com/lagradost/models/EpisodesModel.kt @@ -0,0 +1,19 @@ +package com.lagradost.models + +import com.google.gson.annotations.SerializedName + +data class EpisodesModel( + @SerializedName("views") val views : Int, + @SerializedName("commented") val commented : Int, + @SerializedName("id") val id : String, + @SerializedName("animeId") val animeId : String, + @SerializedName("teamId") val teamId : String, + @SerializedName("volume") val volume : Int, + @SerializedName("episodeNum") val episodeNum : Int, + @SerializedName("subEpisodeNum") val subEpisodeNum : Int, + @SerializedName("lastUpdated") val lastUpdated : String, + @SerializedName("resourcePath") val resourcePath : String, + @SerializedName("playPath") val playPath : String, + @SerializedName("title") val title : String, + @SerializedName("previewPath") val previewPath : String +) diff --git a/AniageProvider/src/main/kotlin/com/lagradost/models/FindModel.kt b/AniageProvider/src/main/kotlin/com/lagradost/models/FindModel.kt new file mode 100644 index 0000000..e2638ec --- /dev/null +++ b/AniageProvider/src/main/kotlin/com/lagradost/models/FindModel.kt @@ -0,0 +1,46 @@ +package com.lagradost.models + +import com.google.gson.annotations.SerializedName + + +data class FindModel ( + + // @SerializedName("cursorNext") val cursorNext : CursorNext?, + // @SerializedName("cursorPrev") val cursorPrev : String?, + @SerializedName("counter") val counter : Int, + @SerializedName("data") val data : List +) + +data class CursorNext ( + + @SerializedName("page") val page : Int, + @SerializedName("pageSize") val pageSize : Int, + @SerializedName("order") val order : Order, + @SerializedName("cleanup") val cleanup : List +) + +data class Data ( + + @SerializedName("id") val id : String, + @SerializedName("posterId") val posterId : String, + @SerializedName("title") val title : String, + @SerializedName("alternativeTitle") val alternativeTitle : String, + @SerializedName("type") val type : String, + @SerializedName("titleStatus") val titleStatus : String, + @SerializedName("publishedAt") val publishedAt : String, + @SerializedName("genres") val genres : List, + @SerializedName("description") val description : String, + @SerializedName("season") val season : String, + @SerializedName("studios") val studios : List, + @SerializedName("adult") val adult : Int, + @SerializedName("episodes") val episodes : Int, + @SerializedName("trailerUrl") val trailerUrl : String, + @SerializedName("maxEpisodes") val maxEpisodes : Int, + @SerializedName("averageDuration") val averageDuration : Int +) + +data class Order ( + + @SerializedName("by") val by : String, + @SerializedName("direction") val direction : String +) \ No newline at end of file diff --git a/AniageProvider/src/main/kotlin/com/lagradost/models/TeamsModel.kt b/AniageProvider/src/main/kotlin/com/lagradost/models/TeamsModel.kt new file mode 100644 index 0000000..df3e8ff --- /dev/null +++ b/AniageProvider/src/main/kotlin/com/lagradost/models/TeamsModel.kt @@ -0,0 +1,19 @@ +package com.lagradost.models + +import com.google.gson.annotations.SerializedName + +data class TeamsModel ( + @SerializedName("socials") val socials : List, + @SerializedName("id") val id : String, + @SerializedName("ownerId") val ownerId : String, + @SerializedName("description") val description : String, + @SerializedName("name") val name : String, + @SerializedName("logo") val logo : String, + @SerializedName("type") val type : String +) + +data class Socials ( + + @SerializedName("url") val url : String, + @SerializedName("type") val type : String +) \ No newline at end of file