diff --git a/src/it/aniplay/AndroidManifest.xml b/src/it/aniplay/AndroidManifest.xml
index 568741e54..c8c0aac73 100644
--- a/src/it/aniplay/AndroidManifest.xml
+++ b/src/it/aniplay/AndroidManifest.xml
@@ -1,2 +1,22 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/it/aniplay/build.gradle b/src/it/aniplay/build.gradle
index f65a7e4da..ad3b4122d 100644
--- a/src/it/aniplay/build.gradle
+++ b/src/it/aniplay/build.gradle
@@ -1,7 +1,11 @@
ext {
extName = 'AniPlay'
extClass = '.AniPlay'
- extVersionCode = 6
+ extVersionCode = 7
}
apply from: "$rootDir/common.gradle"
+
+dependencies {
+ implementation(project(":lib:playlist-utils"))
+}
diff --git a/src/it/aniplay/res/web_hi_res_512.png b/src/it/aniplay/res/web_hi_res_512.png
deleted file mode 100644
index 46c3a588e..000000000
Binary files a/src/it/aniplay/res/web_hi_res_512.png and /dev/null differ
diff --git a/src/it/aniplay/src/eu/kanade/tachiyomi/animeextension/it/aniplay/AniPlay.kt b/src/it/aniplay/src/eu/kanade/tachiyomi/animeextension/it/aniplay/AniPlay.kt
index 7c952a355..0f3865965 100644
--- a/src/it/aniplay/src/eu/kanade/tachiyomi/animeextension/it/aniplay/AniPlay.kt
+++ b/src/it/aniplay/src/eu/kanade/tachiyomi/animeextension/it/aniplay/AniPlay.kt
@@ -1,12 +1,14 @@
package eu.kanade.tachiyomi.animeextension.it.aniplay
import android.app.Application
-import android.content.SharedPreferences
-import android.widget.Toast
-import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
-import eu.kanade.tachiyomi.animeextension.BuildConfig
+import eu.kanade.tachiyomi.animeextension.it.aniplay.dto.AnimeInfoDto
+import eu.kanade.tachiyomi.animeextension.it.aniplay.dto.EpisodeDto
+import eu.kanade.tachiyomi.animeextension.it.aniplay.dto.LatestItemDto
+import eu.kanade.tachiyomi.animeextension.it.aniplay.dto.PopularAnimeDto
+import eu.kanade.tachiyomi.animeextension.it.aniplay.dto.PopularResponseDto
+import eu.kanade.tachiyomi.animeextension.it.aniplay.dto.VideoDto
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
@@ -14,17 +16,17 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
+import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess
-import kotlinx.serialization.json.Json
-import okhttp3.Headers
+import eu.kanade.tachiyomi.util.asJsoup
+import eu.kanade.tachiyomi.util.parseAs
import okhttp3.HttpUrl
-import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
-import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
@@ -32,264 +34,158 @@ class AniPlay : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AniPlay"
- override val baseUrl by lazy { preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! }
+ override val baseUrl = "https://aniplay.co"
override val lang = "it"
- override val supportsLatest = false
+ override val supportsLatest = true
- private val json: Json by injectLazy()
+ override fun headersBuilder() = super.headersBuilder()
+ .add("Referer", "$baseUrl/")
+ .add("Origin", baseUrl)
- private val preferences: SharedPreferences by lazy {
+ override val versionId = 2 // Source was rewritten in Svelte
+
+ private val preferences by lazy {
Injekt.get().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
+ override fun popularAnimeRequest(page: Int) =
+ GET("$API_URL/advancedSearch?sort=7&page=$page&origin=,,,,,,", headers)
- override fun popularAnimeParse(response: Response): AnimesPage = searchAnimeParse(response)
-
- override fun popularAnimeRequest(page: Int): Request =
- GET("$baseUrl/api/anime/advanced-similar-search?page=${page - 1}&size=36&sort=views,desc&sort=id")
+ override fun popularAnimeParse(response: Response): AnimesPage {
+ val parsed = response.parseAs()
+ val animes = parsed.data.map(PopularAnimeDto::toSAnime)
+ return AnimesPage(animes, parsed.pagination.hasNextPage)
+ }
// =============================== Latest ===============================
+ override fun latestUpdatesRequest(page: Int) = GET("$API_URL/latest-episodes?page=$page&type=All")
- override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
-
- override fun latestUpdatesParse(response: Response): AnimesPage = throw UnsupportedOperationException()
+ override fun latestUpdatesParse(response: Response): AnimesPage {
+ val items = response.parseAs>()
+ val animes = items.mapNotNull { it.serie.firstOrNull()?.toSAnime() }
+ return AnimesPage(animes, items.size == 20)
+ }
// =============================== Search ===============================
+ override fun getFilterList() = AniPlayFilters.FILTER_LIST
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
- val params = AniPlayFilters.getSearchParameters(filters)
- return client.newCall(searchAnimeRequest(page, query, params))
- .awaitSuccess()
- .use(::searchAnimeParse)
- }
-
- override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw UnsupportedOperationException()
-
- private fun searchAnimeRequest(page: Int, query: String, filters: AniPlayFilters.FilterSearchParams): Request {
- if ((filters.year.isNotEmpty() && filters.season.isEmpty()) ||
- (filters.year.isEmpty() && filters.season.isNotEmpty())
- ) {
- error("Per gli anime stagionali, seleziona sia l'anno che la stagione")
- }
-
- val url = if (filters.year.isNotEmpty()) {
- "$baseUrl/api/seasonal-view".toHttpUrlOrNull()!!.newBuilder()
- .addPathSegment("${filters.season}-${filters.year}")
- .addQueryParameter("page", (page - 1).toString())
- .addQueryParameter("size", "36")
- .addQueryParameter("sort", filters.order)
- .addQueryParameter("sort", "id")
+ return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
+ val id = query.removePrefix(PREFIX_SEARCH)
+ client.newCall(GET("$baseUrl/series/$id"))
+ .awaitSuccess()
+ .use(::searchAnimeByIdParse)
} else {
- "$baseUrl/api/anime/advanced-similar-search".toHttpUrlOrNull()!!.newBuilder()
- .addQueryParameter("page", (page - 1).toString())
- .addQueryParameter("size", "36")
- .addQueryParameter("sort", filters.order)
- .addQueryParameter("sort", "id")
- .addIfNotBlank("query", query)
- .addIfNotBlank("genreIds", filters.genre)
- .addIfNotBlank("typeIds", filters.type)
- .addIfNotBlank("statusIds", filters.status)
- .addIfNotBlank("originIds", filters.origin)
- .addIfNotBlank("studioIds", filters.studio)
- .addIfNotBlank("startYear", filters.start)
- .addIfNotBlank("endYear", filters.end)
+ super.getSearchAnime(page, query, filters)
}
-
- return GET(url.build().toString())
}
- override fun searchAnimeParse(response: Response): AnimesPage {
- val parsed = json.decodeFromString>(response.body.string())
-
- val animeList = parsed.map { ani ->
- SAnime.create().apply {
- title = ani.title
- if (ani.verticalImages.isNotEmpty()) {
- thumbnail_url = ani.verticalImages.first().imageFull
- }
- url = ani.id.toString()
- description = ani.storyline
- }
- }
-
- return AnimesPage(animeList, animeList.size == 36)
+ private fun searchAnimeByIdParse(response: Response): AnimesPage {
+ val details = animeDetailsParse(response)
+ .apply { setUrlWithoutDomain(response.request.url.toString()) }
+ return AnimesPage(listOf(details), false)
}
- // ============================== Filters ===============================
+ override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
+ val params = AniPlayFilters.getSearchParameters(filters)
+ val url = "$API_URL/advancedSearch".toHttpUrl().newBuilder()
+ .addQueryParameter("page", page.toString())
+ .addQueryParameter("origin", ",,,,,,")
+ .addQueryParameter("sort", params.order)
+ .addIfNotBlank("_q", query)
+ .addIfNotBlank("genres", params.genres)
+ .addIfNotBlank("country", params.countries)
+ .addIfNotBlank("types", params.types)
+ .addIfNotBlank("studios", params.studios)
+ .addIfNotBlank("status", params.status)
+ .addIfNotBlank("subbed", params.languages)
+ .build()
- override fun getFilterList(): AnimeFilterList = AniPlayFilters.FILTER_LIST
+ return GET(url, headers)
+ }
+
+ override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
// =========================== Anime Details ============================
- override fun getAnimeUrl(anime: SAnime) = "$baseUrl/anime/${anime.url}"
+ override fun animeDetailsParse(response: Response) = SAnime.create().apply {
+ val script = response.getPageScript()
+ val jsonString = script.substringAfter("{serie:").substringBefore(",tags") + "}"
+ val parsed = jsonString.fixJsonString().parseAs()
- override fun animeDetailsRequest(anime: SAnime) = GET("$baseUrl/api/anime/${anime.url}")
+ title = parsed.title
+ genre = parsed.genres.joinToString { it.name }
+ artist = parsed.studios.joinToString { it.name }
+ thumbnail_url = parsed.thumbnailUrl
+ status = when (parsed.status) {
+ "Completato" -> SAnime.COMPLETED
+ "In corso" -> SAnime.ONGOING
+ "Sospeso" -> SAnime.ON_HIATUS
+ else -> SAnime.UNKNOWN
+ }
- override fun animeDetailsParse(response: Response): SAnime {
- val detailsJson = json.decodeFromString(response.body.string())
+ description = buildString {
+ parsed.description?.also {
+ append(it, "\n\n")
+ }
- return SAnime.create().apply {
- title = detailsJson.title
- author = detailsJson.studio
- status = parseStatus(detailsJson.status)
- description = buildString {
- append(detailsJson.storyline)
- append("\n\nTipologia: ${detailsJson.type}")
- append("\nOrigine: ${detailsJson.origin}")
- if (detailsJson.startDate != null) append("\nData di inizio: ${detailsJson.startDate}")
- append("\nStato: ${detailsJson.status}")
+ listOf(
+ "Titolo Alternativo" to parsed.alternative,
+ "Origine" to parsed.origin,
+ "Giorno di lancio" to parsed.release_day,
+ ).forEach { (title, value) ->
+ if (value != null) append(title, ": ", value, "\n")
}
}
}
// ============================== Episodes ==============================
-
- override fun episodeListRequest(anime: SAnime): Request = GET("$baseUrl/api/anime/${anime.url}")
-
override fun episodeListParse(response: Response): List {
- val animeJson = json.decodeFromString(response.body.string())
- val episodeList = mutableListOf()
+ val script = response.getPageScript()
+ val jsonString = script.substringAfter(",episodes:").substringBefore("]},") + "]"
+ val parsed = jsonString.fixJsonString().parseAs>()
- if (animeJson.seasons.isNotEmpty()) {
- for (season in animeJson.seasons) {
- val episodesResponse = client.newCall(
- GET("$baseUrl/api/anime/${animeJson.id}/season/${season.id}"),
- ).execute()
- val episodesJson = json.decodeFromString>(episodesResponse.body.string())
-
- episodeList.addAll(
- episodesJson.map { ep ->
- SEpisode.create().apply {
- name = "Episode ${ep.episodeNumber.toIntOrNull() ?: (ep.episodeNumber.toFloatOrNull() ?: 1)} ${ep.title ?: ""}"
- episode_number = ep.episodeNumber.toFloatOrNull() ?: 0F
- if (ep.airingDate != null) date_upload = SimpleDateFormat("yyyy-MM-dd", Locale.ITALY).parse(ep.airingDate)!!.time
- url = ep.id.toString()
- }
- },
- )
+ return parsed.map {
+ SEpisode.create().apply {
+ episode_number = it.number?.toFloatOrNull() ?: 1F
+ url = "/watch/${it.id}"
+ name = it.title ?: "Episodio ${it.number}"
+ date_upload = it.release_date.toDate()
}
- } else if (animeJson.episodes.isNotEmpty()) {
- episodeList.addAll(
- animeJson.episodes.map { ep ->
- SEpisode.create().apply {
- name = "Episode ${ep.episodeNumber.toIntOrNull() ?: (ep.episodeNumber.toFloatOrNull() ?: 1)} ${ep.title ?: ""}"
- episode_number = ep.episodeNumber.toFloatOrNull() ?: 0F
- if (ep.airingDate != null) date_upload = SimpleDateFormat("yyyy-MM-dd", Locale.ITALY).parse(ep.airingDate)!!.time
- url = ep.id.toString()
- }
- },
- )
- }
-
- return episodeList.sortedBy { it.episode_number }.reversed()
+ }.reversed()
}
// ============================ Video Links =============================
-
- override fun videoListRequest(episode: SEpisode): Request = GET("$baseUrl/api/episode/${episode.url}")
+ private val playlistUtils by lazy { PlaylistUtils(client, headers) }
override fun videoListParse(response: Response): List