AoDParser Media handling rework [Part 1/2]

This commit is contained in:
2021-08-31 19:47:18 +02:00
parent a505315781
commit c2a5f768b8
9 changed files with 298 additions and 69 deletions

View File

@ -48,7 +48,8 @@ object AoDParser {
private var csrfToken: String = ""
private var loginSuccess = false
private val mediaList = arrayListOf<Media>() // actual media (data)
private val mediaList = arrayListOf<Media>() // actual media (data) TODO remove
private val aodMediaList = arrayListOf<AoDMedia>() // actual media (data)
// gui media
val guiMediaList = arrayListOf<ItemMedia>()
@ -112,16 +113,38 @@ object AoDParser {
* get a media by it's ID (int)
* @return Media
*/
@Deprecated(message = "Use getMediaById2() instead")
suspend fun getMediaById(aodId: Int): Media {
val media = mediaList.first { it.id == aodId }
if (media.episodes.isEmpty()) {
loadStreams(media).join()
loadMediaAsync(media.id).await()
}
return media
}
/**
* get a media by it's ID (int)
* @param aodId The AoD ID of the requested media
* @return returns a AoDMedia of type Movie or TVShow if found, else return AoDMediaNone
*/
suspend fun getMediaById2(aodId: Int): AoDMedia {
return aodMediaList.firstOrNull { it.aodId == aodId } ?:
try {
loadMediaAsync(aodId).await().apply {
aodMediaList.add(this)
}
} catch (exn:NullPointerException) {
Log.e(javaClass.name, "Error while loading media $aodId", exn)
AoDMediaNone
}
}
/**
* get subscription info from aod website, remove "Anime-Abo" Prefix and trim
*/
@ -417,6 +440,131 @@ object AoDParser {
}
}
private suspend fun loadMediaAsync(aodId: Int): Deferred<AoDMedia> = coroutineScope {
return@coroutineScope async (Dispatchers.IO) {
if (sessionCookies.isEmpty()) login() // TODO is this needed?
// return none object, if login wasn't successful
if (!loginSuccess) {
Log.w(javaClass.name, "Login, was not successful.")
return@async AoDMediaNone
}
// get the media page
val res = Jsoup.connect("$baseUrl/anime/$aodId")
.cookies(sessionCookies)
.get()
// println(res)
if (csrfToken.isEmpty()) {
csrfToken = res.select("meta[name=csrf-token]").attr("content")
Log.d(javaClass.name, "New csrf token is $csrfToken")
}
// playlist parsing TODO can this be async to the genral info marsing?
val besides = res.select("div.besides").first()
val aodPlaylists = besides.select("input.streamstarter_html5").map { streamstarter ->
parsePlaylistAsync(
streamstarter.attr("data-playlist"),
streamstarter.attr("data-lang")
)
}
/**
* generic aod media data
*/
val title = res.select("h1[itemprop=name]").text()
val description = res.select("div[itemprop=description]").text()
val posterURL = res.select("img.fullwidth-image").attr("src")
val type = when {
posterURL.contains("films") -> MediaType.MOVIE
posterURL.contains("series") -> MediaType.TVSHOW
else -> MediaType.OTHER
}
var year = 0
var age = 0
res.select("table.vertical-table").select("tr").forEach { row ->
when (row.select("th").text().lowercase(Locale.ROOT)) {
"produktionsjahr" -> year = row.select("td").text().toInt()
"fsk" -> age = row.select("td").text().toInt()
}
}
// similar titles from media page
val similar = res.select("h2:contains(Ähnliche Animes)").next().select("li").mapNotNull {
val mediaId = it.select("a.thumbs").attr("href")
.substringAfterLast("/").toIntOrNull()
val mediaImage = it.select("a.thumbs > img").attr("src")
val mediaTitle = it.select("a").text()
if (mediaId != null) {
ItemMedia(mediaId, mediaTitle, mediaImage)
} else {
null
}
}
/**
* additional information for episodes:
* description: a short description of the episode
* watched: indicates if the episodes has been watched
* watched callback: url to set watched in aod
*/
val episodesInfo: Map<Int, AoDEpisodeInfo> = if (type == MediaType.TVSHOW) {
res.select("div.three-box-container > div.episodebox").mapNotNull { episodeBox ->
// make sure the episode has a streaming link
if (episodeBox.select("input.streamstarter_html5").isNotEmpty()) {
val mediaId = episodeBox.select("div.flip-front").attr("id").substringAfter("-").toInt()
val episodeShortDesc = episodeBox.select("p.episodebox-shorttext").text()
val episodeWatched = episodeBox.select("div.episodebox-icons > div").hasClass("status-icon-orange")
val episodeWatchedCallback = episodeBox.select("input.streamstarter_html5").eachAttr("data-playlist").first()
AoDEpisodeInfo(mediaId, episodeShortDesc, episodeWatched, episodeWatchedCallback)
} else {
null
}
}.associateBy { it.aodMediaId }
} else {
mapOf()
}
// TODO make AoDPlaylist to teapod playlist
val playlist: List<AoDEpisode> = aodPlaylists.awaitAll().flatMap { aodPlaylist ->
aodPlaylist.list.mapIndexed { index, episode ->
AoDEpisode(
mediaId = episode.mediaid,
title = episode.title,
description = episode.description,
shortDesc = episodesInfo[episode.mediaid]?.shortDesc ?: "",
imageURL = episode.image,
number = index,
watched = episodesInfo[episode.mediaid]?.watched ?: false,
watchedCallback = episodesInfo[episode.mediaid]?.watchedCallback ?: "",
streams = mutableListOf(Stream(episode.sources.first().file, aodPlaylist.language))
)
}
}.groupingBy { it.mediaId }.reduce{ _, accumulator, element ->
accumulator.copy().also {
it.streams.addAll(element.streams)
}
}.values.toList()
println("new playlist object: $playlist")
return@async AoDMedia(
aodId = aodId,
type = type,
title = title,
shortText = description,
posterURL = posterURL,
year = year,
age = age,
similar = similar,
playlist = playlist
)
}
}
/**
* don't use Gson().fromJson() as we don't have any control over the api and it may change
*/