@ -99,10 +99,12 @@ object AoDParser {
/ * *
* initially load all media and home screen data
* /
fun initialLoading ( ) = listOf (
loadHome ( ) ,
listAnimes ( )
)
suspend fun initialLoading ( ) {
coroutineScope {
launch { loadHome ( ) }
launch { listAnimes ( ) }
}
}
/ * *
* get a media by it ' s ID ( int )
@ -121,15 +123,16 @@ object AoDParser {
/ * *
* get subscription info from aod website , remove " Anime-Abo " Prefix and trim
* /
fun getSubscriptionInfoAsync ( ) : Deferred < String > {
return GlobalScope . async ( Dispatchers . IO ) {
// get the subscription page
val res = Jsoup . connect ( baseUrl + subscriptionPath )
. cookies ( sessionCookies )
. get ( )
return @async res . select ( " a:contains(Anime-Abo) " ) . text ( )
. removePrefix ( " Anime-Abo " ) . trim ( )
suspend fun getSubscriptionInfoAsync ( ) : Deferred < String > {
return coroutineScope {
async ( Dispatchers . IO ) {
val res = Jsoup . connect ( baseUrl + subscriptionPath )
. cookies ( sessionCookies )
. get ( )
return @async res . select ( " a:contains(Anime-Abo) " ) . text ( )
. removePrefix ( " Anime-Abo " ) . trim ( )
}
}
}
@ -137,7 +140,7 @@ object AoDParser {
return baseUrl + subscriptionPath
}
fun markAsWatched ( mediaId : Int , episodeId : Int ) = GlobalScope . launch {
suspend fun markAsWatched ( mediaId : Int , episodeId : Int ) {
val episode = getMediaById ( mediaId ) . getEpisodeById ( episodeId )
episode . watched = true
sendCallback ( episode . watchedCallback )
@ -146,137 +149,145 @@ object AoDParser {
}
// TODO don't use jsoup here
private fun sendCallback ( callbackPath : String ) = GlobalScope . launch ( Dispatchers . IO ) {
val headers = mutableMapOf (
Pair ( " Accept " , " application/json, text/javascript, */*; q=0.01 " ) ,
Pair ( " Accept-Language " , " de,en-US;q=0.7,en;q=0.3 " ) ,
Pair ( " Accept-Encoding " , " gzip, deflate, br " ) ,
Pair ( " X-CSRF-Token " , csrfToken ) ,
Pair ( " X-Requested-With " , " XMLHttpRequest " ) ,
)
try {
Jsoup . connect ( baseUrl + callbackPath )
. ignoreContentType ( true )
. cookies ( sessionCookies )
. headers ( headers )
. execute ( )
} catch ( ex : IOException ) {
Log . e ( javaClass . name , " Callback for $callbackPath failed. " , ex )
}
private suspend fun sendCallback ( callbackPath : String ) = coroutineScope {
launch ( Dispatchers . IO ) {
val headers = mutableMapOf (
Pair ( " Accept " , " application/json, text/javascript, */*; q=0.01 " ) ,
Pair ( " Accept-Language " , " de,en-US;q=0.7,en;q=0.3 " ) ,
Pair ( " Accept-Encoding " , " gzip, deflate, br " ) ,
Pair ( " X-CSRF-Token " , csrfToken ) ,
Pair ( " X-Requested-With " , " XMLHttpRequest " ) ,
)
try {
Jsoup . connect ( baseUrl + callbackPath )
. ignoreContentType ( true )
. cookies ( sessionCookies )
. headers ( headers )
. execute ( )
} catch ( ex : IOException ) {
Log . e ( javaClass . name , " Callback for $callbackPath failed. " , ex )
}
}
}
/ * *
* load all media from aod into itemMediaList and mediaList
* TODO private suspend fun listAnimes ( ) = withContext ( Dispatchers . IO ) should also work , maybe a bug in android studio ?
* /
private fun listAnimes ( ) = GlobalScope . launch ( Dispatchers . IO ) {
val resAnimes = Jsoup . connect ( baseUrl + libraryPath ) . get ( )
//println(resAnimes)
itemMediaList . clear ( )
mediaList . clear ( )
resAnimes . select ( " div.animebox " ) . forEach {
val type = if ( it . select ( " p.animebox-link " ) . select ( " a " ) . text ( ) . lowercase ( Locale . ROOT ) == " zur serie " ) {
MediaType . TVSHOW
} else {
MediaType . MOVIE
private suspend fun listAnimes ( ) = withContext ( Dispatchers . IO ) {
launch ( Dispatchers . IO ) {
val resAnimes = Jsoup . connect ( baseUrl + libraryPath ) . get ( )
//println(resAnimes)
itemMediaList . clear ( )
mediaList . clear ( )
resAnimes . select ( " div.animebox " ) . forEach {
val type = if ( it . select ( " p.animebox-link " ) . select ( " a " ) . text ( ) . lowercase ( Locale . ROOT ) == " zur serie " ) {
MediaType . TVSHOW
} else {
MediaType . MOVIE
}
val mediaTitle = it . select ( " h3.animebox-title " ) . text ( )
val mediaLink = it . select ( " p.animebox-link " ) . select ( " a " ) . attr ( " href " )
val mediaImage = it . select ( " p.animebox-image " ) . select ( " img " ) . attr ( " src " )
val mediaShortText = it . select ( " p.animebox-shorttext " ) . text ( )
val mediaId = mediaLink . substringAfterLast ( " / " ) . toInt ( )
itemMediaList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
mediaList . add ( Media ( mediaId , mediaLink , type ) . apply {
info . title = mediaTitle
info . posterUrl = mediaImage
info . shortDesc = mediaShortText
} )
}
val mediaTitle = it . select ( " h3.animebox-title " ) . text ( )
val mediaLink = it . select ( " p.animebox-link " ) . select ( " a " ) . attr ( " href " )
val mediaImage = it . select ( " p.animebox-image " ) . select ( " img " ) . attr ( " src " )
val mediaShortText = it . select ( " p.animebox-shorttext " ) . text ( )
val mediaId = mediaLink . substringAfterLast ( " / " ) . toInt ( )
itemMediaList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
mediaList . add ( Media ( mediaId , mediaLink , type ) . apply {
info . title = mediaTitle
info . posterUrl = mediaImage
info . shortDesc = mediaShortText
} )
}
Log . i ( javaClass . name , " Total library size is: ${mediaList.size} " )
Log . i ( javaClass . name , " Total library size is: ${mediaList.size} " )
}
}
/ * *
* load new episodes , titles and highlights
* /
private fun loadHome ( ) = GlobalScope . launch ( Dispatchers . IO ) {
val resHome = Jsoup . connect ( baseUrl ) . get ( )
// get highlights from AoD
highlightsList . clear ( )
resHome . select ( " #aod-highlights " ) . select ( " div.news-item " ) . forEach {
val mediaId = it . select ( " div.news-item-text " ) . select ( " a.serienlink " )
. attr ( " href " ) . substringAfterLast ( " / " ) . toIntOrNull ( )
val mediaTitle = it . select ( " div.news-title " ) . select ( " h2 " ) . text ( )
val mediaImage = it . select ( " img " ) . attr ( " src " )
if ( mediaId != null ) {
highlightsList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
private suspend fun loadHome ( ) = withContext ( Dispatchers . IO ) {
launch ( Dispatchers . IO ) {
val resHome = Jsoup . connect ( baseUrl ) . get ( )
// get highlights from AoD
highlightsList . clear ( )
resHome . select ( " #aod-highlights " ) . select ( " div.news-item " ) . forEach {
val mediaId = it . select ( " div.news-item-text " ) . select ( " a.serienlink " )
. attr ( " href " ) . substringAfterLast ( " / " ) . toIntOrNull ( )
val mediaTitle = it . select ( " div.news-title " ) . select ( " h2 " ) . text ( )
val mediaImage = it . select ( " img " ) . attr ( " src " )
if ( mediaId != null ) {
highlightsList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
}
}
}
// get all new episodes from AoD
newEpisodesList . clear ( )
resHome . select ( " h2:contains(Neue Episoden) " ) . next ( ) . select ( " li " ) . forEach {
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()} - ${it.select("span.neweps").text()} "
// get all new episodes from AoD
newEpisodesList . clear ( )
resHome . select ( " h2:contains(Neue Episoden) " ) . next ( ) . select ( " li " ) . forEach {
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()} - ${it.select("span.neweps").text()} "
if ( mediaId != null ) {
newEpisodesList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
if ( mediaId != null ) {
newEpisodesList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
}
}
}
// get new simulcasts from AoD
newSimulcastsList . clear ( )
resHome . select ( " h2:contains(Neue Simulcasts) " ) . next ( ) . select ( " li " ) . forEach {
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 ( )
// get new simulcasts from AoD
newSimulcastsList . clear ( )
resHome . select ( " h2:contains(Neue Simulcasts) " ) . next ( ) . select ( " li " ) . forEach {
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 ) {
newSimulcastsList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
if ( mediaId != null ) {
newSimulcastsList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
}
}
}
// get new titles from AoD
newTitlesList . clear ( )
resHome . select ( " h2:contains(Neue Anime-Titel) " ) . next ( ) . select ( " li " ) . forEach {
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 ( )
// get new titles from AoD
newTitlesList . clear ( )
resHome . select ( " h2:contains(Neue Anime-Titel) " ) . next ( ) . select ( " li " ) . forEach {
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 ) {
newTitlesList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
if ( mediaId != null ) {
newTitlesList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
}
}
}
// get top ten from AoD
topTenList . clear ( )
resHome . select ( " h2:contains(Anime Top 10) " ) . next ( ) . select ( " li " ) . forEach {
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 ( )
// get top ten from AoD
topTenList . clear ( )
resHome . select ( " h2:contains(Anime Top 10) " ) . next ( ) . select ( " li " ) . forEach {
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 ) {
topTenList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
if ( mediaId != null ) {
topTenList . add ( ItemMedia ( mediaId , mediaTitle , mediaImage ) )
}
}
}
// if highlights is empty, add a random new title
if ( highlightsList . isEmpty ( ) ) {
if ( newTitlesList . isNotEmpty ( ) ) {
highlightsList . add ( newTitlesList [ Random . nextInt ( 0 , newTitlesList . size ) ] )
} else {
highlightsList . add ( ItemMedia ( 0 , " " , " " ) )
// if highlights is empty, add a random new title
if ( highlightsList . isEmpty ( ) ) {
if ( newTitlesList . isNotEmpty ( ) ) {
highlightsList . add ( newTitlesList [ Random . nextInt ( 0 , newTitlesList . size ) ] )
} else {
highlightsList . add ( ItemMedia ( 0 , " " , " " ) )
}
}
Log . i ( javaClass . name , " loaded home " )
}
}
@ -286,112 +297,114 @@ object AoDParser {
* load streams for the media path , movies have one episode
* @param media is used as call ba reference
* /
private fun loadStreams ( media : Media ) = GlobalScope . launch ( Dispatchers . IO ) {
if ( sessionCookies . isEmpty ( ) ) login ( )
if ( ! loginSuccess ) {
Log . w ( javaClass . name , " Login, was not successful. " )
return @launch
}
private suspend fun loadStreams ( media : Media ) = coroutineScope {
launch ( Dispatchers . IO ) {
if ( sessionCookies . isEmpty ( ) ) login ( )
// get the media page
val res = Jsoup . connect ( baseUrl + media . link )
. cookies ( sessionCookies )
. get ( )
if ( ! loginSuccess ) {
Log . w ( javaClass . name , " Login, was not successful. " )
return @launch
}
//println(res)
// get the media page
val res = Jsoup . connect ( baseUrl + media . link )
. cookies ( sessionCookies )
. get ( )
if ( csrfToken . isEmpty ( ) ) {
csrfToken = res . select ( " meta[name=csrf-token] " ) . attr ( " content " )
//Log.i(javaClass.name, "New csrf token is $csrfToken")
}
//println(res)
val besides = res . select ( " div.besides " ) . first ( )
val playlists = besides . select ( " input.streamstarter_html5 " ) . map { streamstarter ->
parsePlaylistAsync (
streamstarter . attr ( " data-playlist " ) ,
streamstarter . attr ( " data-lang " )
)
} . awaitAll ( )
playlists . forEach { aod ->
// TODO improve language handling
val locale = when ( aod . extLanguage ) {
" ger " -> Locale . GERMAN
" jap " -> Locale . JAPANESE
else -> Locale . ROOT
if ( csrfToken . isEmpty ( ) ) {
csrfToken = res . select ( " meta[name=csrf-token] " ) . attr ( " content " )
//Log.i(javaClass.name, "New csrf token is $csrfToken")
}
aod . playlist . forEach { ep ->
try {
if ( media . hasEpisode ( ep . mediaid ) ) {
media . getEpisodeById ( ep . mediaid ) . streams . add (
Stream ( ep . sources . first ( ) . file , locale )
)
} else {
media . episodes . add ( Episode (
id = ep . mediaid ,
streams = mutableListOf ( Stream ( ep . sources . first ( ) . file , locale ) ) ,
posterUrl = ep . image ,
title = ep . title ,
description = ep . description ,
number = getNumberFromTitle ( ep . title , media . type )
) )
val besides = res . select ( " div.besides " ) . first ( )
val playlists = besides . select ( " input.streamstarter_html5 " ) . map { streamstarter ->
parsePlaylistAsync (
streamstarter . attr ( " data-playlist " ) ,
streamstarter . attr ( " data-lang " )
)
} . awaitAll ( )
playlists . forEach { aod ->
// TODO improve language handling
val locale = when ( aod . extLanguage ) {
" ger " -> Locale . GERMAN
" jap " -> Locale . JAPANESE
else -> Locale . ROOT
}
aod . playlist . forEach { ep ->
try {
if ( media . hasEpisode ( ep . mediaid ) ) {
media . getEpisodeById ( ep . mediaid ) . streams . add (
Stream ( ep . sources . first ( ) . file , locale )
)
} else {
media . episodes . add ( Episode (
id = ep . mediaid ,
streams = mutableListOf ( Stream ( ep . sources . first ( ) . file , locale ) ) ,
posterUrl = ep . image ,
title = ep . title ,
description = ep . description ,
number = getNumberFromTitle ( ep . title , media . type )
) )
}
} catch ( ex : Exception ) {
Log . w ( javaClass . name , " Could not parse episode information. " , ex )
}
} catch ( ex : Exception ) {
Log . w ( javaClass . name , " Could not parse episode information. " , ex )
}
}
}
Log . i ( javaClass . name , " Loaded playlists successfully " )
// additional info from the media page
res . select ( " table.vertical-table " ) . select ( " tr " ) . forEach { row ->
when ( row . select ( " th " ) . text ( ) . lowercase ( Locale . ROOT ) ) {
" produktionsjahr " -> media . info . year = row . select ( " td " ) . text ( ) . toInt ( )
" fsk " -> media . info . age = row . select ( " td " ) . text ( ) . toInt ( )
" episodenanzahl " -> {
media . info . episodesCount = row . select ( " td " ) . text ( )
. substringBefore ( " / " )
. filter { it . isDigit ( ) }
. toInt ( )
Log . i ( javaClass . name , " Loaded playlists successfully " )
// additional info from the media page
res . select ( " table.vertical-table " ) . select ( " tr " ) . forEach { row ->
when ( row . select ( " th " ) . text ( ) . lowercase ( Locale . ROOT ) ) {
" produktionsjahr " -> media . info . year = row . select ( " td " ) . text ( ) . toInt ( )
" fsk " -> media . info . age = row . select ( " td " ) . text ( ) . toInt ( )
" episodenanzahl " -> {
media . info . episodesCount = row . select ( " td " ) . text ( )
. substringBefore ( " / " )
. filter { it . isDigit ( ) }
. toInt ( )
}
}
}
}
// similar titles from media page
media . info . 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
// similar titles from media page
media . info . 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 tv shows the episode title (description) is loaded from the "api"
if ( media . type == MediaType . TVSHOW ) {
res . select ( " div.three-box-container > div.episodebox " ) . forEach { episodebox ->
// make sure the episode has a streaming link
if ( episodebox . select ( " input.streamstarter_html5 " ) . isNotEmpty ( ) ) {
val episodeId = 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 ( )
media . episodes . firstOrNull { it . id == episodeId } ?. apply {
shortDesc = episodeShortDesc
watched = episodeWatched
watchedCallback = episodeWatchedCallback
// additional information for tv shows the episode title (description) is loaded from the "api"
if ( media . type == MediaType . TVSHOW ) {
res . select ( " div.three-box-container > div.episodebox " ) . forEach { episodebox ->
// make sure the episode has a streaming link
if ( episodebox . select ( " input.streamstarter_html5 " ) . isNotEmpty ( ) ) {
val episodeId = 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 ( )
media . episodes . firstOrNull { it . id == episodeId } ?. apply {
shortDesc = episodeShortDesc
watched = episodeWatched
watchedCallback = episodeWatchedCallback
}
}
}
}
Log . i ( javaClass . name , " media loaded successfully " )
}
Log . i ( javaClass . name , " media loaded successfully " )
}
/ * *
@ -402,7 +415,7 @@ object AoDParser {
return CompletableDeferred ( AoDObject ( listOf ( ) , language ) )
}
return GlobalScope . async ( Dispatchers . IO ) {
return CoroutineScope ( Dispatchers . IO ) . async ( Dispatchers . IO ) {
val headers = mutableMapOf (
Pair ( " Accept " , " application/json, text/javascript, */*; q=0.01 " ) ,
Pair ( " Accept-Language " , " de,en-US;q=0.7,en;q=0.3 " ) ,