fixing anilist stuff

This commit is contained in:
LagradOst 2022-04-03 00:06:35 +02:00
parent 2a663ccef1
commit a933aa8493
8 changed files with 192 additions and 105 deletions

View file

@ -7,11 +7,9 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.loadExtractor
import org.jsoup.Jsoup
import java.util.*
import kotlin.collections.ArrayList
class AnimekisaProvider : MainAPI() {
override var mainUrl = "https://animekisa.in"
override var name = "Animekisa"
override val hasMainPage = true
@ -23,7 +21,7 @@ class AnimekisaProvider : MainAPI() {
TvType.Anime,
)
data class Response (
data class Response(
@JsonProperty("html") val html: String
)
@ -72,11 +70,15 @@ class AnimekisaProvider : MainAPI() {
if (items.size <= 0) throw ErrorLoadingException()
return HomePageResponse(items)
}
override suspend fun search(query: String): List<SearchResponse> {
return app.get("$mainUrl/search/?keyword=$query").document.select("div.flw-item").map {
val title = it.selectFirst("h3 a").text()
val url = it.selectFirst("a.film-poster-ahref").attr("href")
.replace("watch/","anime/").replace(Regex("(-episode-(\\d+)\\/\$|-episode-(\\d+)\$|-episode-full|-episode-.*-.(\\/|))"),"")
.replace("watch/", "anime/").replace(
Regex("(-episode-(\\d+)\\/\$|-episode-(\\d+)\$|-episode-full|-episode-.*-.(\\/|))"),
""
)
val poster = it.selectFirst(".film-poster img").attr("data-src")
AnimeSearchResponse(
title,
@ -91,18 +93,24 @@ class AnimekisaProvider : MainAPI() {
)
}.toList()
}
override suspend fun load(url: String): LoadResponse {
val doc = app.get(url, timeout = 120).document
val poster = doc.selectFirst(".mb-2 img").attr("src") ?: doc.selectFirst("head meta[property=og:image]").attr("content")
val poster = doc.selectFirst(".mb-2 img").attr("src")
?: doc.selectFirst("head meta[property=og:image]").attr("content")
val title = doc.selectFirst("h1.heading-name a").text()
val description = doc.selectFirst("div.description p").text().trim()
val genres = doc.select("div.row-line a").map { it.text() }
val test = if (doc.selectFirst("div.dp-i-c-right").toString().contains("Airing")) ShowStatus.Ongoing else ShowStatus.Completed
val test = if (doc.selectFirst("div.dp-i-c-right").toString()
.contains("Airing")
) ShowStatus.Ongoing else ShowStatus.Completed
val episodes = doc.select("div.tab-content ul li.nav-item").map {
val link = it.selectFirst("a").attr("href")
AnimeEpisode(link)
}
val type = if (doc.selectFirst(".dp-i-stats").toString().contains("Movies")) TvType.AnimeMovie else TvType.Anime
val type = if (doc.selectFirst(".dp-i-stats").toString()
.contains("Movies")
) TvType.AnimeMovie else TvType.Anime
return newAnimeLoadResponse(title, url, type) {
posterUrl = poster
addEpisodes(DubStatus.Subbed, episodes)

View file

@ -70,7 +70,9 @@ interface SyncAPI : OAuth2API {
var nextAiring: SyncNextAiring? = null,
var studio: List<String>? = null,
var genres: List<String>? = null,
var synonyms: List<String>? = null,
var trailerUrl: String? = null,
var isAdult : Boolean? = null,
/** In unixtime */
var startDate: Long? = null,

View file

@ -2,10 +2,12 @@ package com.lagradost.cloudstream3.syncproviders
import com.lagradost.cloudstream3.ErrorLoadingException
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.safeApiCall
class SyncRepo(private val repo: SyncAPI) {
val idPrefix get() = repo.idPrefix
val idPrefix = repo.idPrefix
val name = repo.name
suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource<Boolean> {
return safeApiCall { repo.score(id, status) }
@ -22,4 +24,8 @@ class SyncRepo(private val repo: SyncAPI) {
suspend fun search(query : String) : Resource<List<SyncAPI.SyncSearchResult>> {
return safeApiCall { repo.search(query) ?: throw ErrorLoadingException() }
}
fun hasAccount() : Boolean {
return normalSafeApiCall { repo.loginInfo() != null } ?: false
}
}

View file

@ -73,9 +73,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
override suspend fun search(name: String): List<SyncAPI.SyncSearchResult>? {
val data = searchShows(name) ?: return null
return data.data.Page.media.map {
return data.data?.Page?.media?.map {
SyncAPI.SyncSearchResult(
it.title.romaji,
it.title.romaji ?: return null,
this.name,
it.id.toString(),
"$mainUrl/anime/${it.id}",
@ -92,10 +92,15 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
season.id.toString(),
nextAiring = season.nextAiringEpisode?.let {
SyncAPI.SyncNextAiring(
it.episode,
it.timeUntilAiring + unixTime
it.episode ?: return@let null,
(it.timeUntilAiring ?: return@let null) + unixTime
)
},
genres = season.genres,
synonyms = season.synonyms,
isAdult = season.isAdult,
totalEpisodes = season.episodes,
//synopsis = season.
//TODO REST
)
}
@ -107,7 +112,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
return SyncAPI.SyncStatus(
score = data.score,
watchedEpisodes = data.episodes,
status = data.type.value,
status = data.type?.value ?: return null,
isFavorite = data.isFavourite,
)
}
@ -254,7 +259,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
)
}
filtered?.forEach {
if (fixName(it.title.romaji) == fixName(name)) return it
it.title.romaji?.let { romaji ->
if (fixName(romaji) == fixName(name)) return it
}
}
return filtered?.firstOrNull()
@ -267,7 +274,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
Paused(2),
Dropped(3),
Planning(4),
Rewatching(5),
ReWatching(5),
None(-1)
}
@ -279,7 +286,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
2 -> AniListStatusType.Paused
3 -> AniListStatusType.Dropped
4 -> AniListStatusType.Planning
5 -> AniListStatusType.Rewatching
5 -> AniListStatusType.ReWatching
else -> AniListStatusType.None
}
}
@ -290,22 +297,26 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
private suspend fun getSeason(id: Int): SeasonResponse? {
val q: String = """
val q = """
query (${'$'}id: Int = $id) {
Media (id: ${'$'}id, type: ANIME) {
id
idMal
coverImage
duration
episodes
genres
synonyms
averageScore
isAdult
trailer
relations {
edges {
id
relationType(version: 2)
node {
id
format
nextAiringEpisode {
timeUntilAiring
episode
}
coverImage
}
}
}
@ -369,8 +380,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
val data = postApi(q, true)
val d = mapper.readValue<GetDataRoot>(data ?: return null)
val main = d.data.Media
if (main.mediaListEntry != null) {
val main = d.data?.Media
if (main?.mediaListEntry != null) {
return AniListTitleHolder(
title = main.title,
id = id,
@ -382,11 +393,11 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
)
} else {
return AniListTitleHolder(
title = main.title,
title = main?.title,
id = id,
isFavourite = main.isFavourite,
isFavourite = main?.isFavourite,
progress = 0,
episodes = main.episodes,
episodes = main?.episodes,
score = 0,
type = AniListStatusType.None,
)
@ -420,14 +431,14 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
data class MediaRecommendation(
@JsonProperty("id") val id: Int,
@JsonProperty("title") val title: Title,
@JsonProperty("title") val title: Title?,
@JsonProperty("idMal") val idMal: Int?,
@JsonProperty("coverImage") val coverImage: CoverImage,
@JsonProperty("coverImage") val coverImage: CoverImage?,
@JsonProperty("averageScore") val averageScore: Int?
)
data class FullAnilistList(
@JsonProperty("data") val data: Data
@JsonProperty("data") val data: Data?
)
data class CompletedAt(
@ -448,7 +459,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
)
data class CoverImage(
@JsonProperty("medium") val medium: String,
@JsonProperty("medium") val medium: String?,
@JsonProperty("large") val large: String?
)
@ -629,11 +640,11 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
val data = postApi(q)
if (data == "") return null
val userData = mapper.readValue<AniListRoot>(data ?: return null)
val u = userData.data.Viewer
val u = userData.data?.Viewer
val user = AniListUser(
u.id,
u.name,
u.avatar.large,
u?.id,
u?.name,
u?.avatar?.large,
)
if (setSettings) {
setKey(accountId, ANILIST_USER_KEY, user)
@ -652,9 +663,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
val season = getSeason(id)
if (season != null) {
seasons.add(season)
if (season.data.Media.format?.startsWith("TV") == true) {
season.data.Media.relations.edges.forEach {
if (it.node.format != null) {
if (season.data?.Media?.format?.startsWith("TV") == true) {
season.data.Media.relations?.edges?.forEach {
if (it.node?.format != null) {
if (it.relationType == "SEQUEL" && it.node.format.startsWith("TV")) {
getSeasonRecursive(it.node.id)
return@forEach
@ -669,34 +680,56 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
data class SeasonResponse(
@JsonProperty("data") val data: SeasonData,
@JsonProperty("data") val data: SeasonData?,
)
data class SeasonData(
@JsonProperty("Media") val Media: SeasonMedia,
@JsonProperty("Media") val Media: SeasonMedia?,
)
data class SeasonMedia(
@JsonProperty("id") val id: Int,
@JsonProperty("id") val id: Int?,
@JsonProperty("idMal") val idMal: Int?,
@JsonProperty("format") val format: String?,
@JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?,
@JsonProperty("relations") val relations: SeasonEdges,
@JsonProperty("relations") val relations: SeasonEdges?,
@JsonProperty("coverImage") val coverImage: MediaCoverImage?,
@JsonProperty("duration") val duration: Int?,
@JsonProperty("episodes") val episodes: Int?,
@JsonProperty("genres") val genres: List<String>?,
@JsonProperty("synonyms") val synonyms: List<String>?,
@JsonProperty("averageScore") val averageScore: Int?,
@JsonProperty("isAdult") val isAdult: Boolean?,
@JsonProperty("trailer") val trailer: MediaTrailer?,
)
data class MediaTrailer(
@JsonProperty("id") val id: String?,
@JsonProperty("site") val site: String?,
@JsonProperty("thumbnail") val thumbnail: String?,
)
data class MediaCoverImage(
@JsonProperty("extraLarge") val extraLarge: String?,
@JsonProperty("large") val large: String?,
@JsonProperty("medium") val medium: String?,
@JsonProperty("color") val color: String?,
)
data class SeasonNextAiringEpisode(
@JsonProperty("episode") val episode: Int,
@JsonProperty("timeUntilAiring") val timeUntilAiring: Int,
@JsonProperty("episode") val episode: Int?,
@JsonProperty("timeUntilAiring") val timeUntilAiring: Int?,
)
data class SeasonEdges(
@JsonProperty("edges") val edges: List<SeasonEdge>,
@JsonProperty("edges") val edges: List<SeasonEdge>?,
)
data class SeasonEdge(
@JsonProperty("id") val id: Int,
@JsonProperty("relationType") val relationType: String,
@JsonProperty("node") val node: SeasonNode,
@JsonProperty("id") val id: Int?,
@JsonProperty("relationType") val relationType: String?,
@JsonProperty("node") val node: SeasonNode?,
)
data class AniListFavoritesMediaConnection(
@ -710,121 +743,121 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
data class SeasonNode(
@JsonProperty("id") val id: Int,
@JsonProperty("format") val format: String?,
@JsonProperty("title") val title: Title,
@JsonProperty("title") val title: Title?,
@JsonProperty("idMal") val idMal: Int?,
@JsonProperty("coverImage") val coverImage: CoverImage,
@JsonProperty("coverImage") val coverImage: CoverImage?,
@JsonProperty("averageScore") val averageScore: Int?
// @JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?,
)
data class AniListAvatar(
@JsonProperty("large") val large: String,
@JsonProperty("large") val large: String?,
)
data class AniListViewer(
@JsonProperty("id") val id: Int,
@JsonProperty("name") val name: String,
@JsonProperty("avatar") val avatar: AniListAvatar,
@JsonProperty("favourites") val favourites: AniListFavourites,
@JsonProperty("id") val id: Int?,
@JsonProperty("name") val name: String?,
@JsonProperty("avatar") val avatar: AniListAvatar?,
@JsonProperty("favourites") val favourites: AniListFavourites?,
)
data class AniListData(
@JsonProperty("Viewer") val Viewer: AniListViewer,
@JsonProperty("Viewer") val Viewer: AniListViewer?,
)
data class AniListRoot(
@JsonProperty("data") val data: AniListData,
@JsonProperty("data") val data: AniListData?,
)
data class AniListUser(
@JsonProperty("id") val id: Int,
@JsonProperty("name") val name: String,
@JsonProperty("picture") val picture: String,
@JsonProperty("id") val id: Int?,
@JsonProperty("name") val name: String?,
@JsonProperty("picture") val picture: String?,
)
data class LikeNode(
@JsonProperty("id") val id: Int,
@JsonProperty("id") val id: Int?,
//@JsonProperty("idMal") public int idMal;
)
data class LikePageInfo(
@JsonProperty("total") val total: Int,
@JsonProperty("currentPage") val currentPage: Int,
@JsonProperty("lastPage") val lastPage: Int,
@JsonProperty("perPage") val perPage: Int,
@JsonProperty("hasNextPage") val hasNextPage: Boolean,
@JsonProperty("total") val total: Int?,
@JsonProperty("currentPage") val currentPage: Int?,
@JsonProperty("lastPage") val lastPage: Int?,
@JsonProperty("perPage") val perPage: Int?,
@JsonProperty("hasNextPage") val hasNextPage: Boolean?,
)
data class LikeAnime(
@JsonProperty("nodes") val nodes: List<LikeNode>,
@JsonProperty("pageInfo") val pageInfo: LikePageInfo,
@JsonProperty("nodes") val nodes: List<LikeNode>?,
@JsonProperty("pageInfo") val pageInfo: LikePageInfo?,
)
data class LikeFavourites(
@JsonProperty("anime") val anime: LikeAnime,
@JsonProperty("anime") val anime: LikeAnime?,
)
data class LikeViewer(
@JsonProperty("favourites") val favourites: LikeFavourites,
@JsonProperty("favourites") val favourites: LikeFavourites?,
)
data class LikeData(
@JsonProperty("Viewer") val Viewer: LikeViewer,
@JsonProperty("Viewer") val Viewer: LikeViewer?,
)
data class LikeRoot(
@JsonProperty("data") val data: LikeData,
@JsonProperty("data") val data: LikeData?,
)
data class Recommendation(
@JsonProperty("title") val title: String,
@JsonProperty("idMal") val idMal: Int,
@JsonProperty("poster") val poster: String,
@JsonProperty("title") val title: String?,
@JsonProperty("idMal") val idMal: Int?,
@JsonProperty("poster") val poster: String?,
@JsonProperty("averageScore") val averageScore: Int?
)
data class AniListTitleHolder(
@JsonProperty("title") val title: Title,
@JsonProperty("isFavourite") val isFavourite: Boolean,
@JsonProperty("id") val id: Int,
@JsonProperty("progress") val progress: Int,
@JsonProperty("episodes") val episodes: Int,
@JsonProperty("score") val score: Int,
@JsonProperty("type") val type: AniListStatusType,
@JsonProperty("title") val title: Title?,
@JsonProperty("isFavourite") val isFavourite: Boolean?,
@JsonProperty("id") val id: Int?,
@JsonProperty("progress") val progress: Int?,
@JsonProperty("episodes") val episodes: Int?,
@JsonProperty("score") val score: Int?,
@JsonProperty("type") val type: AniListStatusType?,
)
data class GetDataMediaListEntry(
@JsonProperty("progress") val progress: Int,
@JsonProperty("status") val status: String,
@JsonProperty("score") val score: Int,
@JsonProperty("progress") val progress: Int?,
@JsonProperty("status") val status: String?,
@JsonProperty("score") val score: Int?,
)
data class Nodes(
@JsonProperty("id") val id: Int,
@JsonProperty("id") val id: Int?,
@JsonProperty("mediaRecommendation") val mediaRecommendation: MediaRecommendation?
)
data class GetDataMedia(
@JsonProperty("isFavourite") val isFavourite: Boolean,
@JsonProperty("episodes") val episodes: Int,
@JsonProperty("title") val title: Title,
@JsonProperty("isFavourite") val isFavourite: Boolean?,
@JsonProperty("episodes") val episodes: Int?,
@JsonProperty("title") val title: Title?,
@JsonProperty("mediaListEntry") val mediaListEntry: GetDataMediaListEntry?
)
data class Recommendations(
@JsonProperty("nodes") val nodes: List<Nodes>
@JsonProperty("nodes") val nodes: List<Nodes>?
)
data class GetDataData(
@JsonProperty("Media") val Media: GetDataMedia,
@JsonProperty("Media") val Media: GetDataMedia?,
)
data class GetDataRoot(
@JsonProperty("data") val data: GetDataData,
@JsonProperty("data") val data: GetDataData?,
)
data class GetSearchTitle(
@JsonProperty("romaji") val romaji: String,
@JsonProperty("romaji") val romaji: String?,
)
data class TrailerObject(
@ -845,18 +878,18 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
@JsonProperty("trailer") val trailer: TrailerObject?,
@JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?,
@JsonProperty("recommendations") val recommendations: Recommendations?,
@JsonProperty("relations") val relations: SeasonEdges
@JsonProperty("relations") val relations: SeasonEdges?
)
data class GetSearchPage(
@JsonProperty("Page") val Page: GetSearchData,
@JsonProperty("Page") val Page: GetSearchData?,
)
data class GetSearchData(
@JsonProperty("media") val media: List<GetSearchMedia>,
@JsonProperty("media") val media: List<GetSearchMedia>?,
)
data class GetSearchRoot(
@JsonProperty("data") val data: GetSearchPage,
@JsonProperty("data") val data: GetSearchPage?,
)
}

View file

@ -1191,19 +1191,24 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
syncModel.setEpisodesDelta(-1)
}
result_sync_current_episodes?.doOnTextChanged { text, start, before, count ->
if(count == before) return@doOnTextChanged
result_sync_current_episodes?.doOnTextChanged { text, _, before, count ->
if (count == before) return@doOnTextChanged
text?.toString()?.toIntOrNull()?.let { ep ->
syncModel.setEpisodes(ep)
}
}
}
observe(syncModel.synced) { list ->
result_sync_names?.text =
list.filter { it.isSynced && it.hasAccount }.joinToString { it.name }
}
observe(syncModel.metadata) { meta ->
when (meta) {
is Resource.Success -> {
val d = meta.value
result_sync_episodes?.max = (d.totalEpisodes ?: 0)*1000
result_sync_episodes?.max = (d.totalEpisodes ?: 0) * 1000
normalSafeApiCall {
val ctx = result_sync_max_episodes?.context
result_sync_max_episodes?.text =

View file

@ -13,6 +13,13 @@ import com.lagradost.cloudstream3.utils.SyncUtil
import kotlinx.coroutines.launch
data class CurrentSynced(
val name: String,
val idPrefix: String,
val isSynced: Boolean,
val hasAccount: Boolean,
)
class SyncViewModel : ViewModel() {
private val repos = SyncApis
@ -29,19 +36,42 @@ class SyncViewModel : ViewModel() {
// prefix, id
private val syncIds = hashMapOf<String, String>()
private val _currentSynced: MutableLiveData<List<CurrentSynced>> =
MutableLiveData(getMissing())
// pair of name idPrefix isSynced
val synced: LiveData<List<CurrentSynced>> get() = _currentSynced
private fun getMissing(): List<CurrentSynced> {
return repos.map {
CurrentSynced(
it.name,
it.idPrefix,
syncIds.containsKey(it.idPrefix),
it.hasAccount()
)
}
}
private fun updateSynced() {
_currentSynced.postValue(getMissing())
}
fun setMalId(id: String?) {
syncIds[malApi.idPrefix] = id ?: return
updateSynced()
}
fun setAniListId(id: String?) {
syncIds[aniListApi.idPrefix] = id ?: return
updateSynced()
}
fun addFromUrl(url : String?) = viewModelScope.launch {
fun addFromUrl(url: String?) = viewModelScope.launch {
SyncUtil.getIdsFromUrl(url)?.let { (malId, aniListId) ->
setMalId(malId)
setAniListId(aniListId)
if(malId != null || aniListId != null) {
if (malId != null || aniListId != null) {
updateMetaAndUser()
}
}
@ -101,7 +131,7 @@ class SyncViewModel : ViewModel() {
updateUserData()
}
fun updateUserData() = viewModelScope.launch {
private fun updateUserData() = viewModelScope.launch {
_userDataResponse.postValue(Resource.Loading())
var lastError: Resource<SyncAPI.SyncStatus> = Resource.Failure(false, null, null, "No data")
for ((prefix, id) in syncIds) {
@ -118,7 +148,7 @@ class SyncViewModel : ViewModel() {
_userDataResponse.postValue(lastError)
}
fun updateMetadata() = viewModelScope.launch {
private fun updateMetadata() = viewModelScope.launch {
_metaResponse.postValue(Resource.Loading())
var lastError: Resource<SyncAPI.SyncResult> = Resource.Failure(false, null, null, "No data")
for ((prefix, id) in syncIds) {

View file

@ -3,9 +3,12 @@ package com.lagradost.cloudstream3.ui.search
import com.lagradost.cloudstream3.SearchQuality
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.syncproviders.OAuth2API
import com.lagradost.cloudstream3.syncproviders.SyncAPI
class SyncSearchViewModel {
private val repos = OAuth2API.SyncApis
data class SyncSearchResultSearchResponse(
override val name: String,
override val url: String,

View file

@ -19,7 +19,7 @@
android:layout_height="wrap_content">
<TextView
android:visibility="gone"
android:id="@+id/result_sync_names"
android:textStyle="bold"
android:textSize="16sp"
android:layout_marginBottom="10dp"