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