From e69fe1d6c2b9c011e26dc32976adb0986132426a Mon Sep 17 00:00:00 2001 From: Alexander Rufenach Date: Wed, 2 Jun 2021 15:54:19 -0400 Subject: [PATCH] Added rewatching support --- Jellyfin.Plugin.AnilistSync/API/AnilistApi.cs | 33 +++++++++---- Jellyfin.Plugin.AnilistSync/API/ApiModel.cs | 3 ++ .../Services/PlaybackScrobbler.cs | 46 ++++++++++++++----- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/Jellyfin.Plugin.AnilistSync/API/AnilistApi.cs b/Jellyfin.Plugin.AnilistSync/API/AnilistApi.cs index 3cfed15..6322828 100644 --- a/Jellyfin.Plugin.AnilistSync/API/AnilistApi.cs +++ b/Jellyfin.Plugin.AnilistSync/API/AnilistApi.cs @@ -24,13 +24,14 @@ namespace Jellyfin.Plugin.AnilistSync.API private readonly IHttpClientFactory _httpClientFactory; private readonly JsonSerializerOptions _jsonSerializerOptions; - private const string listIdQuery = @"mutation ($mediaId: Int) {SaveMediaListEntry(mediaId: $mediaId) {id, status}}&variables={""mediaId"": ""{0}""}"; - private const string listUpdateQuery = @"mutation ($id: Int, $progress: Int, $status: MediaListStatus) {SaveMediaListEntry(id: $id, progress: $progress, status: $status) {id, progress, status}}"; + private const string listIdQuery = @"mutation ($mediaId: Int) {SaveMediaListEntry(mediaId: $mediaId) {id, status, repeat}}&variables={""mediaId"": ""{0}""}"; + private const string listUpdateQuery = @"mutation ($id: Int, $progress: Int, $status: MediaListStatus, $repeat: Int) {SaveMediaListEntry(id: $id, progress: $progress, status: $status, repeat: $repeat) {id, progress, status, repeat}}"; private const string episodeQuery = @"query ($id: Int) {Media (id: $id) {episodes}}"; private const string currentUserQuery = @"query {Viewer {id, name}}"; private const string listUpdateVars1 = @"&variables={""id"":""{0}"", ""progress"":""{1}""}"; private const string listUpdateVars2 = @"&variables={""id"":""{0}"", ""progress"":""{1}"", ""status"":""{2}""}"; + private const string listUpdateVars3 = @"&variables={""id"":""{0}"", ""progress"":""{1}"", ""status"":""{2}"", ""repeat"":""{3}""}"; public const string BaseOauthUrl = @"https://anilist.co/api/v2"; public const string BaseGraphQLUrl = @"https://graphql.anilist.co/api/v2?query="; @@ -60,7 +61,9 @@ namespace Jellyfin.Plugin.AnilistSync.API public async Task GetUser(string? userToken) { var requestMessage = new HttpRequestMessage(); - requestMessage.RequestUri = new Uri(BaseGraphQLUrl + currentUserQuery); + requestMessage.RequestUri = new Uri( + BaseGraphQLUrl + + currentUserQuery); requestMessage.Method = HttpMethod.Post; requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", userToken); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); @@ -76,10 +79,12 @@ namespace Jellyfin.Plugin.AnilistSync.API return data; } - public async Task GetListId(string anilistId, string? userToken) + public async Task GetListEntry(string anilistId, string? userToken) { var requestMessage = new HttpRequestMessage(); - requestMessage.RequestUri = new Uri(BaseGraphQLUrl + listIdQuery.Replace("{0}", anilistId)); + requestMessage.RequestUri = new Uri( + BaseGraphQLUrl + + listIdQuery.Replace("{0}", anilistId)); requestMessage.Method = HttpMethod.Post; requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", userToken); requestMessage.Content = new StringContent("", Encoding.UTF8, "application/json"); @@ -95,7 +100,10 @@ namespace Jellyfin.Plugin.AnilistSync.API public async Task GetEpisodes(string anilistId) { var requestMessage = new HttpRequestMessage(); - requestMessage.RequestUri = new Uri(BaseGraphQLUrl + episodeQuery + $"&variables={{\"id\":{anilistId}}}"); + requestMessage.RequestUri = new Uri( + BaseGraphQLUrl + + episodeQuery + + $"&variables={{\"id\":{anilistId}}}"); requestMessage.Method = HttpMethod.Post; requestMessage.Content = new StringContent("", Encoding.UTF8, "application/json"); var responseMessage = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(requestMessage); @@ -107,12 +115,17 @@ namespace Jellyfin.Plugin.AnilistSync.API return data; } - public async Task PostListUpdate(string anilistId, string? userToken, int? progress, MediaListStatus status) + public async Task PostListUpdate(string? anilistMediaId, string? userToken, int? progress, MediaListStatus? status, int? timesRewatched) { - var listEntry = GetListId(anilistId, userToken).Result?.Data?.ListEntry; - var requestMessage = new HttpRequestMessage(); - requestMessage.RequestUri = new Uri(BaseGraphQLUrl + listUpdateQuery + listUpdateVars2.Replace("{0}", listEntry?.Id.ToString()).Replace("{1}", progress.ToString()).Replace("{2}", status.ToString())); + requestMessage.RequestUri = new Uri( + BaseGraphQLUrl + + listUpdateQuery + + listUpdateVars3 + .Replace("{0}", anilistMediaId) + .Replace("{1}", progress.ToString()) + .Replace("{2}", status.ToString()) + .Replace("{3}", timesRewatched.ToString())); requestMessage.Method = HttpMethod.Post; requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", userToken); requestMessage.Content = new StringContent("", Encoding.UTF8, "application/json"); diff --git a/Jellyfin.Plugin.AnilistSync/API/ApiModel.cs b/Jellyfin.Plugin.AnilistSync/API/ApiModel.cs index 75c42ea..1970595 100644 --- a/Jellyfin.Plugin.AnilistSync/API/ApiModel.cs +++ b/Jellyfin.Plugin.AnilistSync/API/ApiModel.cs @@ -71,6 +71,9 @@ namespace Jellyfin.Plugin.AnilistSync.API [JsonPropertyName("status")] public MediaListStatus? Status { get; set; } + + [JsonPropertyName("repeat")] + public int? TimesRewatched { get; set; } } public enum MediaListStatus diff --git a/Jellyfin.Plugin.AnilistSync/Services/PlaybackScrobbler.cs b/Jellyfin.Plugin.AnilistSync/Services/PlaybackScrobbler.cs index b7fb3d8..ba39332 100644 --- a/Jellyfin.Plugin.AnilistSync/Services/PlaybackScrobbler.cs +++ b/Jellyfin.Plugin.AnilistSync/Services/PlaybackScrobbler.cs @@ -73,7 +73,6 @@ namespace Jellyfin.Plugin.AnilistSync.Services } // Checks if runtime is greater than min length to be scrobbled - // TODO: chan 5 to configurable value if (runtime < 60 * 10000 * userConfig.MinLength) { return false; @@ -163,21 +162,46 @@ namespace Jellyfin.Plugin.AnilistSync.Services anilistId); - // Send post request to API to update list - int? episodes = (await _anilistApi.GetEpisodes(anilistId))?.Data?.Media?.Episodes; + // Get the list entry for current watching show from Anilist + var listEntry = _anilistApi.GetListEntry(anilistId, userConfig.UserToken).Result?.Data?.ListEntry; + int? currentIndex = eventArgs.Item.IndexNumber; - _logger.LogInformation("Total Episodes: " + episodes.ToString()); - _logger.LogInformation("Current Episode: " + currentIndex.ToString()); + int? timesRewatched = listEntry?.TimesRewatched; + MediaListStatus? status = listEntry?.Status; - MediaListStatus status = MediaListStatus.CURRENT; + // Check if STARTING a rewatch + if (status == MediaListStatus.COMPLETED) + { + if (currentIndex == 1) // Check rewatching first episode + { + status = MediaListStatus.REPEATING; + } + else + { + _logger.LogDebug("Attempting to start rewatch from middle episode, discarding scrobble"); + return; + } + } + + // Get total number of episodes of current item from Anilist + int? episodes = (await _anilistApi.GetEpisodes(anilistId))?.Data?.Media?.Episodes; + _logger.LogInformation("Total Episodes: {totalEpisodes}", episodes); + _logger.LogInformation("Current Episode: {currentEpisode}", currentIndex); + + // If watching LAST episode change status to completed if (currentIndex == episodes) { + if (status == MediaListStatus.REPEATING) + { + timesRewatched += 1; + } status = MediaListStatus.COMPLETED; } - _logger.LogInformation(status.ToString()); + _logger.LogInformation("Current watch status: {status}", status); - var response = await _anilistApi.PostListUpdate(anilistId, userConfig.UserToken, currentIndex, status); + // Send post request to API to update list + var response = await _anilistApi.PostListUpdate(listEntry?.Id.ToString(), userConfig.UserToken, currentIndex, status, timesRewatched); _logger.LogDebug("Scrobbled without errors"); _lastScrobbled[eventArgs.Session.Id] = eventArgs.MediaInfo.Id; @@ -186,11 +210,11 @@ namespace Jellyfin.Plugin.AnilistSync.Services //{ // _logger.LogDebug("Deleted user token"); //} - catch (AnilistAPIException alEx) + catch (AnilistAPIException aniAPIEx) { - for (int i = 0; i < alEx.errors?.Length; i++) + for (int i = 0; i < aniAPIEx.errors?.Length; i++) { - Error? error = alEx.errors[i]; + Error? error = aniAPIEx.errors[i]; _logger.LogError(error.ErrorMessage, "API response code " + error.ErrorStatus); } }