mirror of
https://github.com/jellyfin/jellyfin-plugin-trakt.git
synced 2024-11-23 05:40:13 +00:00
Enable analyzers, fix all warnings
This commit is contained in:
parent
0985b9455c
commit
9cc1a6f746
@ -1,30 +1,34 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktEpisode
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the season number.
|
||||
/// The trakt.tv episode class.
|
||||
/// </summary>
|
||||
[JsonPropertyName("season")]
|
||||
public int? Season { get; set; }
|
||||
public class TraktEpisode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the season number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("season")]
|
||||
public int? Season { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the episode number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("number")]
|
||||
public int? Number { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episode number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("number")]
|
||||
public int? Number { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the episode title.
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episode title.
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the episode ids.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktEpisodeId Ids { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episode ids.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktEpisodeId Ids { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktEpisodeId : TraktTVId
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The trakt.tv episode id class.
|
||||
/// </summary>
|
||||
public class TraktEpisodeId : TraktTVId
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktIMDBandTMDBId : TraktId
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[JsonPropertyName("imdb")]
|
||||
public string Imdb { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv IDMb and TMDb id class.
|
||||
/// </summary>
|
||||
public class TraktIMDBandTMDBId : TraktId
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the IMDb id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("imdb")]
|
||||
public string Imdb { get; set; }
|
||||
|
||||
[JsonPropertyName("tmdb")]
|
||||
public int? Tmdb { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the TMDb id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tmdb")]
|
||||
public int? Tmdb { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktId
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Trakt item id.
|
||||
/// The trakt.tv id class.
|
||||
/// </summary>
|
||||
[JsonPropertyName("trakt")]
|
||||
public int? Trakt { get; set; }
|
||||
public class TraktId
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the trakt.tv item id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("trakt")]
|
||||
public int? Trakt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item slug.
|
||||
/// </summary>
|
||||
[JsonPropertyName("slug")]
|
||||
public string Slug { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the item slug.
|
||||
/// </summary>
|
||||
[JsonPropertyName("slug")]
|
||||
public string Slug { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,28 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktMovie
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv movie class.
|
||||
/// </summary>
|
||||
public class TraktMovie
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the movie title.
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("year")]
|
||||
public int? Year { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the movie year.
|
||||
/// </summary>
|
||||
[JsonPropertyName("year")]
|
||||
public int? Year { get; set; }
|
||||
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktMovieId Ids { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the movie ids.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktMovieId Ids { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktMovieId : TraktIMDBandTMDBId
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The trakt.tv movie id class.
|
||||
/// </summary>
|
||||
public class TraktMovieId : TraktIMDBandTMDBId
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktPerson
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv person class.
|
||||
/// </summary>
|
||||
public class TraktPerson
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the person name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktPersonId Ids { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the person ids.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktPersonId Ids { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,16 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktPersonId : TraktIMDBandTMDBId
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[JsonPropertyName("tvrage")]
|
||||
public int? Tvrage { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv person id class.
|
||||
/// </summary>
|
||||
public class TraktPersonId : TraktIMDBandTMDBId
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the TVRage person id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tvrage")]
|
||||
public int? Tvrage { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public abstract class TraktRated
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[JsonPropertyName("rating")]
|
||||
public int? Rating { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv rated class.
|
||||
/// </summary>
|
||||
public abstract class TraktRated
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the rating.
|
||||
/// </summary>
|
||||
[JsonPropertyName("rating")]
|
||||
public int? Rating { get; set; }
|
||||
|
||||
[JsonPropertyName("rated_at")]
|
||||
public string RatedAt { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the rating date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("rated_at")]
|
||||
public string RatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktSeason
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[JsonPropertyName("number")]
|
||||
public int? Number { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv season class.
|
||||
/// </summary>
|
||||
public class TraktSeason
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the season number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("number")]
|
||||
public int? Number { get; set; }
|
||||
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktSeasonId Ids { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the season ids.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktSeasonId Ids { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,28 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktSeasonId : TraktId
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[JsonPropertyName("tmdb")]
|
||||
public int? Tmdb { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv season id class.
|
||||
/// </summary>
|
||||
public class TraktSeasonId : TraktId
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the season TMDb id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tmdb")]
|
||||
public int? Tmdb { get; set; }
|
||||
|
||||
[JsonPropertyName("tvdb")]
|
||||
public int? Tvdb { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the season TVDb id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tvdb")]
|
||||
public int? Tvdb { get; set; }
|
||||
|
||||
[JsonPropertyName("tvrage")]
|
||||
public int? Tvrage { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the season TVRage id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tvrage")]
|
||||
public int? Tvrage { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,28 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktShow
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv show class.
|
||||
/// </summary>
|
||||
public class TraktShow
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the show title.
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("year")]
|
||||
public int? Year { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the show year.
|
||||
/// </summary>
|
||||
[JsonPropertyName("year")]
|
||||
public int? Year { get; set; }
|
||||
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktShowId Ids { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the show ids.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktShowId Ids { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktShowId : TraktTVId
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The trakt.tv show id class.
|
||||
/// </summary>
|
||||
public class TraktShowId : TraktTVId
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
public class TraktTVId : TraktIMDBandTMDBId
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[JsonPropertyName("tvdb")]
|
||||
public int? Tvdb { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv tv id class.
|
||||
/// </summary>
|
||||
public class TraktTVId : TraktIMDBandTMDBId
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the TVDb id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tvdb")]
|
||||
public int? Tvdb { get; set; }
|
||||
|
||||
[JsonPropertyName("tvrage")]
|
||||
public int? Tvrage { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the TVRage id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tvrage")]
|
||||
public int? Tvrage { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,28 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Scrobble;
|
||||
|
||||
public class SocialMedia
|
||||
namespace Trakt.Api.DataContracts.Scrobble
|
||||
{
|
||||
[JsonPropertyName("facebook")]
|
||||
public bool Facebook { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv social media class.
|
||||
/// </summary>
|
||||
public class SocialMedia
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether facebook posting should be enabled.
|
||||
/// </summary>
|
||||
[JsonPropertyName("facebook")]
|
||||
public bool Facebook { get; set; }
|
||||
|
||||
[JsonPropertyName("twitter")]
|
||||
public bool Twitter { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether twittwe posting should be enabled.
|
||||
/// </summary>
|
||||
[JsonPropertyName("twitter")]
|
||||
public bool Twitter { get; set; }
|
||||
|
||||
[JsonPropertyName("tumblr")]
|
||||
public bool Tumblr { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether tumblr posting should be enabled.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tumblr")]
|
||||
public bool Tumblr { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,41 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Scrobble;
|
||||
|
||||
public class TraktScrobbleEpisode
|
||||
namespace Trakt.Api.DataContracts.Scrobble
|
||||
{
|
||||
[JsonPropertyName("show")]
|
||||
public TraktShow Show { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv episode scrobble class.
|
||||
/// </summary>
|
||||
public class TraktScrobbleEpisode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the show.
|
||||
/// </summary>
|
||||
[JsonPropertyName("show")]
|
||||
public TraktShow Show { get; set; }
|
||||
|
||||
[JsonPropertyName("episode")]
|
||||
public TraktEpisode Episode { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episode.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episode")]
|
||||
public TraktEpisode Episode { get; set; }
|
||||
|
||||
[JsonPropertyName("progress")]
|
||||
public float Progress { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the progress.
|
||||
/// </summary>
|
||||
[JsonPropertyName("progress")]
|
||||
public float Progress { get; set; }
|
||||
|
||||
[JsonPropertyName("app_version")]
|
||||
public string AppVersion { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the app version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("app_version")]
|
||||
public string AppVersion { get; set; }
|
||||
|
||||
[JsonPropertyName("app_date")]
|
||||
public string AppDate { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the app date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("app_date")]
|
||||
public string AppDate { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,35 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Scrobble;
|
||||
|
||||
public class TraktScrobbleMovie
|
||||
namespace Trakt.Api.DataContracts.Scrobble
|
||||
{
|
||||
[JsonPropertyName("movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv movie scrobble class.
|
||||
/// </summary>
|
||||
public class TraktScrobbleMovie
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the movie.
|
||||
/// </summary>
|
||||
[JsonPropertyName("movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
|
||||
[JsonPropertyName("progress")]
|
||||
public float Progress { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the progress.
|
||||
/// </summary>
|
||||
[JsonPropertyName("progress")]
|
||||
public float Progress { get; set; }
|
||||
|
||||
[JsonPropertyName("app_version")]
|
||||
public string AppVersion { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the app versin.
|
||||
/// </summary>
|
||||
[JsonPropertyName("app_version")]
|
||||
public string AppVersion { get; set; }
|
||||
|
||||
[JsonPropertyName("app_date")]
|
||||
public string AppDate { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the app date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("app_date")]
|
||||
public string AppDate { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,53 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Scrobble;
|
||||
|
||||
public class TraktScrobbleResponse
|
||||
namespace Trakt.Api.DataContracts.Scrobble
|
||||
{
|
||||
[JsonPropertyName("action")]
|
||||
public string Action { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv scrobble response class.
|
||||
/// </summary>
|
||||
public class TraktScrobbleResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonPropertyName("progress")]
|
||||
public float Progress { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the action.
|
||||
/// </summary>
|
||||
[JsonPropertyName("action")]
|
||||
public string Action { get; set; }
|
||||
|
||||
[JsonPropertyName("sharing")]
|
||||
public SocialMedia Sharing { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the progress.
|
||||
/// </summary>
|
||||
[JsonPropertyName("progress")]
|
||||
public float Progress { get; set; }
|
||||
|
||||
[JsonPropertyName("movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the sharing options.
|
||||
/// </summary>
|
||||
[JsonPropertyName("sharing")]
|
||||
public SocialMedia Sharing { get; set; }
|
||||
|
||||
[JsonPropertyName("episode")]
|
||||
public TraktEpisode Episode { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the movie.
|
||||
/// </summary>
|
||||
[JsonPropertyName("movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
|
||||
[JsonPropertyName("show")]
|
||||
public TraktShow Show { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episode.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episode")]
|
||||
public TraktEpisode Episode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the show.
|
||||
/// </summary>
|
||||
[JsonPropertyName("show")]
|
||||
public TraktShow Show { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,43 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection;
|
||||
|
||||
public class TraktEpisodeCollected : TraktEpisode
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection
|
||||
{
|
||||
[JsonPropertyName("collected_at")]
|
||||
public string CollectedAt { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync episodes collected class.
|
||||
/// </summary>
|
||||
public class TraktEpisodeCollected : TraktEpisode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the colletion date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("collected_at")]
|
||||
public string CollectedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("media_type")]
|
||||
public string MediaType { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the media type.
|
||||
/// </summary>
|
||||
[JsonPropertyName("media_type")]
|
||||
public string MediaType { get; set; }
|
||||
|
||||
[JsonPropertyName("resolution")]
|
||||
public string Resolution { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the resolution.
|
||||
/// </summary>
|
||||
[JsonPropertyName("resolution")]
|
||||
public string Resolution { get; set; }
|
||||
|
||||
[JsonPropertyName("audio")]
|
||||
public string Audio { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the audio.
|
||||
/// </summary>
|
||||
[JsonPropertyName("audio")]
|
||||
public string Audio { get; set; }
|
||||
|
||||
[JsonPropertyName("audio_channels")]
|
||||
public string AudioChannels { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of audio channels.
|
||||
/// </summary>
|
||||
[JsonPropertyName("audio_channels")]
|
||||
public string AudioChannels { get; set; }
|
||||
|
||||
// public bool 3d { get; set; }
|
||||
// public bool 3d { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,43 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection;
|
||||
|
||||
public class TraktMovieCollected : TraktMovie
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection
|
||||
{
|
||||
[JsonPropertyName("collected_at")]
|
||||
public string CollectedAt { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync movies collected class.
|
||||
/// </summary>
|
||||
public class TraktMovieCollected : TraktMovie
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the collection date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("collected_at")]
|
||||
public string CollectedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("media_type")]
|
||||
public string MediaType { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the media type.
|
||||
/// </summary>
|
||||
[JsonPropertyName("media_type")]
|
||||
public string MediaType { get; set; }
|
||||
|
||||
[JsonPropertyName("resolution")]
|
||||
public string Resolution { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the resolution.
|
||||
/// </summary>
|
||||
[JsonPropertyName("resolution")]
|
||||
public string Resolution { get; set; }
|
||||
|
||||
[JsonPropertyName("audio")]
|
||||
public string Audio { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the audio.
|
||||
/// </summary>
|
||||
[JsonPropertyName("audio")]
|
||||
public string Audio { get; set; }
|
||||
|
||||
[JsonPropertyName("audio_channels")]
|
||||
public string AudioChannels { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of audio channels.
|
||||
/// </summary>
|
||||
[JsonPropertyName("audio_channels")]
|
||||
public string AudioChannels { get; set; }
|
||||
|
||||
// public bool 3d { get; set; }
|
||||
// public bool 3d { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,26 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection;
|
||||
|
||||
public class TraktSeasonCollected
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection
|
||||
{
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync seasons collected class.
|
||||
/// </summary>
|
||||
public class TraktSeasonCollected
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the season number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TraktEpisodeCollected> Episodes { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episodes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TraktEpisodeCollected> Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,21 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection;
|
||||
|
||||
public class TraktShowCollected : TraktShow
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection
|
||||
{
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<TraktSeasonCollected> Seasons { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync show collected class.
|
||||
/// </summary>
|
||||
public class TraktShowCollected : TraktShow
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the seasons.
|
||||
/// </summary>
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<TraktSeasonCollected> Seasons { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,40 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync;
|
||||
|
||||
public class Items
|
||||
namespace Trakt.Api.DataContracts.Sync
|
||||
{
|
||||
[JsonPropertyName("movies")]
|
||||
public int Movies { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync items class.
|
||||
/// </summary>
|
||||
public class Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the movies.
|
||||
/// </summary>
|
||||
[JsonPropertyName("movies")]
|
||||
public int Movies { get; set; }
|
||||
|
||||
[JsonPropertyName("shows")]
|
||||
public int Shows { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the shows.
|
||||
/// </summary>
|
||||
[JsonPropertyName("shows")]
|
||||
public int Shows { get; set; }
|
||||
|
||||
[JsonPropertyName("seasons")]
|
||||
public int Seasons { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the seasons.
|
||||
/// </summary>
|
||||
[JsonPropertyName("seasons")]
|
||||
public int Seasons { get; set; }
|
||||
|
||||
[JsonPropertyName("episodes")]
|
||||
public int Episodes { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episodes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episodes")]
|
||||
public int Episodes { get; set; }
|
||||
|
||||
[JsonPropertyName("people")]
|
||||
public int People { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the people.
|
||||
/// </summary>
|
||||
[JsonPropertyName("people")]
|
||||
public int People { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,45 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync;
|
||||
|
||||
public class NotFoundObjects
|
||||
namespace Trakt.Api.DataContracts.Sync
|
||||
{
|
||||
[JsonPropertyName("movies")]
|
||||
public List<TraktMovie> Movies { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync not found objects class.
|
||||
/// </summary>
|
||||
public class NotFoundObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the movies.
|
||||
/// </summary>
|
||||
[JsonPropertyName("movies")]
|
||||
public List<TraktMovie> Movies { get; set; }
|
||||
|
||||
[JsonPropertyName("shows")]
|
||||
public List<TraktShow> Shows { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the shows.
|
||||
/// </summary>
|
||||
[JsonPropertyName("shows")]
|
||||
public List<TraktShow> Shows { get; set; }
|
||||
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TraktEpisode> Episodes { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episodes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TraktEpisode> Episodes { get; set; }
|
||||
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<TraktSeason> Seasons { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the seasons.
|
||||
/// </summary>
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<TraktSeason> Seasons { get; set; }
|
||||
|
||||
[JsonPropertyName("people")]
|
||||
public List<TraktPerson> People { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the people.
|
||||
/// </summary>
|
||||
[JsonPropertyName("people")]
|
||||
public List<TraktPerson> People { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,23 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings;
|
||||
|
||||
public class TraktEpisodeRated : TraktRated
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings
|
||||
{
|
||||
[JsonPropertyName("number")]
|
||||
public int? Number { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync episode rated class.
|
||||
/// </summary>
|
||||
public class TraktEpisodeRated : TraktRated
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the episode number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("number")]
|
||||
public int? Number { get; set; }
|
||||
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktEpisodeId Ids { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the ids.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktEpisodeId Ids { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,29 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings;
|
||||
|
||||
public class TraktMovieRated : TraktRated
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings
|
||||
{
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync movie rated class.
|
||||
/// </summary>
|
||||
public class TraktMovieRated : TraktRated
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the title.
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("year")]
|
||||
public int? Year { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the year.
|
||||
/// </summary>
|
||||
[JsonPropertyName("year")]
|
||||
public int? Year { get; set; }
|
||||
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktMovieId Ids { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the ids.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktMovieId Ids { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,27 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings;
|
||||
|
||||
public class TraktSeasonRated : TraktRated
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings
|
||||
{
|
||||
[JsonPropertyName("number")]
|
||||
public int? Number { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync season rated class.
|
||||
/// </summary>
|
||||
public class TraktSeasonRated : TraktRated
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the season number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("number")]
|
||||
public int? Number { get; set; }
|
||||
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TraktEpisodeRated> Episodes { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episodes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TraktEpisodeRated> Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,39 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings;
|
||||
|
||||
public class TraktShowRated : TraktRated
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings
|
||||
{
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync show rated class.
|
||||
/// </summary>
|
||||
public class TraktShowRated : TraktRated
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the title.
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("year")]
|
||||
public int? Year { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the year.
|
||||
/// </summary>
|
||||
[JsonPropertyName("year")]
|
||||
public int? Year { get; set; }
|
||||
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktShowId Ids { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the ids.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ids")]
|
||||
public TraktShowId Ids { get; set; }
|
||||
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<TraktSeasonRated> Seasons { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the seasons.
|
||||
/// </summary>
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<TraktSeasonRated> Seasons { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,35 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync;
|
||||
|
||||
public class TraktSync<TMovie, TShow, TEpisode>
|
||||
namespace Trakt.Api.DataContracts.Sync
|
||||
{
|
||||
[JsonPropertyName("movies")]
|
||||
public List<TMovie> Movies { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync class.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMovie">The type of the movie.</typeparam>
|
||||
/// <typeparam name="TShow">The type of the show.</typeparam>
|
||||
/// <typeparam name="TEpisode">The type of the episode.</typeparam>
|
||||
public class TraktSync<TMovie, TShow, TEpisode>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the movies.
|
||||
/// </summary>
|
||||
[JsonPropertyName("movies")]
|
||||
public List<TMovie> Movies { get; set; }
|
||||
|
||||
[JsonPropertyName("shows")]
|
||||
public List<TShow> Shows { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the shows.
|
||||
/// </summary>
|
||||
[JsonPropertyName("shows")]
|
||||
public List<TShow> Shows { get; set; }
|
||||
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TEpisode> Episodes { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episodes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TEpisode> Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
using Trakt.Api.DataContracts.Sync.Collection;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync;
|
||||
|
||||
public class TraktSyncCollected : TraktSync<TraktMovieCollected, TraktShowCollected, TraktEpisodeCollected>
|
||||
namespace Trakt.Api.DataContracts.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// The trakt.tv sync collected class.
|
||||
/// </summary>
|
||||
public class TraktSyncCollected : TraktSync<TraktMovieCollected, TraktShowCollected, TraktEpisodeCollected>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
using Trakt.Api.DataContracts.Sync.Ratings;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync;
|
||||
|
||||
public class TraktSyncRated : TraktSync<TraktMovieRated, TraktShowRated, TraktEpisodeRated>
|
||||
namespace Trakt.Api.DataContracts.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// The trakt.tv sync rated class.
|
||||
/// </summary>
|
||||
public class TraktSyncRated : TraktSync<TraktMovieRated, TraktShowRated, TraktEpisodeRated>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,34 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync;
|
||||
|
||||
public class TraktSyncResponse
|
||||
namespace Trakt.Api.DataContracts.Sync
|
||||
{
|
||||
[JsonPropertyName("added")]
|
||||
public Items Added { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync response class.
|
||||
/// </summary>
|
||||
public class TraktSyncResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the added items.
|
||||
/// </summary>
|
||||
[JsonPropertyName("added")]
|
||||
public Items Added { get; set; }
|
||||
|
||||
[JsonPropertyName("deleted")]
|
||||
public Items Deleted { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the deleted items.
|
||||
/// </summary>
|
||||
[JsonPropertyName("deleted")]
|
||||
public Items Deleted { get; set; }
|
||||
|
||||
[JsonPropertyName("existing")]
|
||||
public Items Existing { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the existing items.
|
||||
/// </summary>
|
||||
[JsonPropertyName("existing")]
|
||||
public Items Existing { get; set; }
|
||||
|
||||
[JsonPropertyName("not_found")]
|
||||
public NotFoundObjects NotFound { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the not found items.
|
||||
/// </summary>
|
||||
[JsonPropertyName("not_found")]
|
||||
public NotFoundObjects NotFound { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
using Trakt.Api.DataContracts.Sync.Watched;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync;
|
||||
|
||||
public class TraktSyncWatched : TraktSync<TraktMovieWatched, TraktShowWatched, TraktEpisodeWatched>
|
||||
namespace Trakt.Api.DataContracts.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// The trakt.tv sync watched class.
|
||||
/// </summary>
|
||||
public class TraktSyncWatched : TraktSync<TraktMovieWatched, TraktShowWatched, TraktEpisodeWatched>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched;
|
||||
|
||||
public class TraktEpisodeWatched : TraktEpisode
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched
|
||||
{
|
||||
[JsonPropertyName("watched_at")]
|
||||
public string WatchedAt { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync episode watched class.
|
||||
/// </summary>
|
||||
public class TraktEpisodeWatched : TraktEpisode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the watched date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("watched_at")]
|
||||
public string WatchedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched;
|
||||
|
||||
public class TraktMovieWatched : TraktMovie
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched
|
||||
{
|
||||
[JsonPropertyName("watched_at")]
|
||||
public string WatchedAt { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync movie watched class.
|
||||
/// </summary>
|
||||
public class TraktMovieWatched : TraktMovie
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the watched date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("watched_at")]
|
||||
public string WatchedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,27 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched;
|
||||
|
||||
public class TraktSeasonWatched : TraktSeason
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched
|
||||
{
|
||||
[JsonPropertyName("watched_at")]
|
||||
public string WatchedAt { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync season watched class.
|
||||
/// </summary>
|
||||
public class TraktSeasonWatched : TraktSeason
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the watched date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("watched_at")]
|
||||
public string WatchedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TraktEpisodeWatched> Episodes { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episodes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TraktEpisodeWatched> Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,27 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched;
|
||||
|
||||
public class TraktShowWatched : TraktShow
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched
|
||||
{
|
||||
[JsonPropertyName("watched_at")]
|
||||
public string WatchedAt { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv sync show watched class.
|
||||
/// </summary>
|
||||
public class TraktShowWatched : TraktShow
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the watched date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("watched_at")]
|
||||
public string WatchedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<TraktSeasonWatched> Seasons { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the seasons.
|
||||
/// </summary>
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<TraktSeasonWatched> Seasons { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,40 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts;
|
||||
|
||||
public class TraktDeviceCode
|
||||
namespace Trakt.Api.DataContracts
|
||||
{
|
||||
[JsonPropertyName("device_code")]
|
||||
public string DeviceCode { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv device code class.
|
||||
/// </summary>
|
||||
public class TraktDeviceCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the device code.
|
||||
/// </summary>
|
||||
[JsonPropertyName("device_code")]
|
||||
public string DeviceCode { get; set; }
|
||||
|
||||
[JsonPropertyName("user_code")]
|
||||
public string UserCode { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the user code.
|
||||
/// </summary>
|
||||
[JsonPropertyName("user_code")]
|
||||
public string UserCode { get; set; }
|
||||
|
||||
[JsonPropertyName("verification_url")]
|
||||
public string VerificationUrl { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the verification URL.
|
||||
/// </summary>
|
||||
[JsonPropertyName("verification_url")]
|
||||
public string VerificationUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("expires_in")]
|
||||
public int ExpiresIn { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the expiration.
|
||||
/// </summary>
|
||||
[JsonPropertyName("expires_in")]
|
||||
public int ExpiresIn { get; set; }
|
||||
|
||||
[JsonPropertyName("interval")]
|
||||
public int Interval { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the interval.
|
||||
/// </summary>
|
||||
[JsonPropertyName("interval")]
|
||||
public int Interval { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,54 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts;
|
||||
|
||||
public class TraktUserAccessToken
|
||||
namespace Trakt.Api.DataContracts
|
||||
{
|
||||
[JsonPropertyName("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv user access token class.
|
||||
/// </summary>
|
||||
public class TraktUserAccessToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the access token.
|
||||
/// </summary>
|
||||
[JsonPropertyName("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
[JsonPropertyName("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the token type.
|
||||
/// </summary>
|
||||
[JsonPropertyName("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
|
||||
[JsonPropertyName("expires_in")]
|
||||
public int ExpiresIn { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the expiration.
|
||||
/// </summary>
|
||||
[JsonPropertyName("expires_in")]
|
||||
public int ExpiresIn { get; set; }
|
||||
|
||||
[JsonPropertyName("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the refresh token.
|
||||
/// </summary>
|
||||
[JsonPropertyName("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
[JsonPropertyName("scope")]
|
||||
public string Scope { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the scope.
|
||||
/// </summary>
|
||||
[JsonPropertyName("scope")]
|
||||
public string Scope { get; set; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public int CreatedAt { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the creation date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("created_at")]
|
||||
public int CreatedAt { get; set; }
|
||||
|
||||
// Expiration can be a bit of a problem with Trakt. It's usually 90 days, but it's unclear when the
|
||||
// refresh_token expires, so leave a little buffer for expiration...
|
||||
[JsonPropertyName("expirationWithBuffer")]
|
||||
public int ExpirationWithBuffer => ExpiresIn * 3 / 4;
|
||||
/// <summary>
|
||||
/// Gets the expiration time.
|
||||
/// </summary>
|
||||
// Expiration can be a bit of a problem with Trakt. It's usually 90 days, but it's unclear when the
|
||||
// refresh_token expires, so leave a little buffer for expiration...
|
||||
[JsonPropertyName("expirationWithBuffer")]
|
||||
public int ExpirationWithBuffer => ExpiresIn * 3 / 4;
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,40 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts;
|
||||
|
||||
public class TraktUserRefreshTokenRequest
|
||||
namespace Trakt.Api.DataContracts
|
||||
{
|
||||
[JsonPropertyName("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv user refresh token class.
|
||||
/// </summary>
|
||||
public class TraktUserRefreshTokenRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the refresh token.
|
||||
/// </summary>
|
||||
[JsonPropertyName("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
[JsonPropertyName("client_id")]
|
||||
public string ClientId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the client id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("client_id")]
|
||||
public string ClientId { get; set; }
|
||||
|
||||
[JsonPropertyName("client_secret")]
|
||||
public string ClientSecret { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the client secret.
|
||||
/// </summary>
|
||||
[JsonPropertyName("client_secret")]
|
||||
public string ClientSecret { get; set; }
|
||||
|
||||
[JsonPropertyName("redirect_uri")]
|
||||
public string RedirectUri { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the redirect URI.
|
||||
/// </summary>
|
||||
[JsonPropertyName("redirect_uri")]
|
||||
public string RedirectUri { get; set; }
|
||||
|
||||
[JsonPropertyName("grant_type")]
|
||||
public string GrantType { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the grant type.
|
||||
/// </summary>
|
||||
[JsonPropertyName("grant_type")]
|
||||
public string GrantType { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,28 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Collection;
|
||||
|
||||
public class TraktEpisodeCollected
|
||||
namespace Trakt.Api.DataContracts.Users.Collection
|
||||
{
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users episode collected class.
|
||||
/// </summary>
|
||||
public class TraktEpisodeCollected
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the episode number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[JsonPropertyName("collected_at")]
|
||||
public string CollectedAt { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the collection date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("collected_at")]
|
||||
public string CollectedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public TraktMetadata Metadata { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the metadata.
|
||||
/// </summary>
|
||||
[JsonPropertyName("metadata")]
|
||||
public TraktMetadata Metadata { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,36 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Collection;
|
||||
|
||||
public class TraktMetadata
|
||||
namespace Trakt.Api.DataContracts.Users.Collection
|
||||
{
|
||||
[JsonPropertyName("media_type")]
|
||||
public string MediaType { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users metadata class.
|
||||
/// </summary>
|
||||
public class TraktMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the media type.
|
||||
/// </summary>
|
||||
[JsonPropertyName("media_type")]
|
||||
public string MediaType { get; set; }
|
||||
|
||||
[JsonPropertyName("resolution")]
|
||||
public string Resolution { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the resolution.
|
||||
/// </summary>
|
||||
[JsonPropertyName("resolution")]
|
||||
public string Resolution { get; set; }
|
||||
|
||||
[JsonPropertyName("audio")]
|
||||
public string Audio { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the audio.
|
||||
/// </summary>
|
||||
[JsonPropertyName("audio")]
|
||||
public string Audio { get; set; }
|
||||
|
||||
[JsonPropertyName("audio_channels")]
|
||||
public string AudioChannels { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of audio channels.
|
||||
/// </summary>
|
||||
[JsonPropertyName("audio_channels")]
|
||||
public string AudioChannels { get; set; }
|
||||
|
||||
// public bool 3d { get; set; }
|
||||
// public bool 3d { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,29 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Collection;
|
||||
|
||||
public class TraktMovieCollected
|
||||
namespace Trakt.Api.DataContracts.Users.Collection
|
||||
{
|
||||
[JsonPropertyName("collected_at")]
|
||||
public string CollectedAt { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users movie collected class.
|
||||
/// </summary>
|
||||
public class TraktMovieCollected
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the last collection date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("collected_at")]
|
||||
public string CollectedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public TraktMetadata Metadata { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the metadata.
|
||||
/// </summary>
|
||||
[JsonPropertyName("metadata")]
|
||||
public TraktMetadata Metadata { get; set; }
|
||||
|
||||
[JsonPropertyName("movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the movie.
|
||||
/// </summary>
|
||||
[JsonPropertyName("movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,26 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Collection;
|
||||
|
||||
public class TraktSeasonCollected
|
||||
namespace Trakt.Api.DataContracts.Users.Collection
|
||||
{
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users season collected class.
|
||||
/// </summary>
|
||||
public class TraktSeasonCollected
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the season unumber.
|
||||
/// </summary>
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TraktEpisodeCollected> Episodes { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episodes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<TraktEpisodeCollected> Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,33 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Collection;
|
||||
|
||||
public class TraktShowCollected
|
||||
namespace Trakt.Api.DataContracts.Users.Collection
|
||||
{
|
||||
[JsonPropertyName("last_collected_at")]
|
||||
public string LastCollectedAt { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users show collected class.
|
||||
/// </summary>
|
||||
public class TraktShowCollected
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the last collected date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("last_collected_at")]
|
||||
public string LastCollectedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("show")]
|
||||
public TraktShow Show { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the show.
|
||||
/// </summary>
|
||||
[JsonPropertyName("show")]
|
||||
public TraktShow Show { get; set; }
|
||||
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<TraktSeasonCollected> Seasons { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the seasons.
|
||||
/// </summary>
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<TraktSeasonCollected> Seasons { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings;
|
||||
|
||||
public class TraktEpisodeRated : TraktRated
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings
|
||||
{
|
||||
[JsonPropertyName("episode")]
|
||||
public TraktEpisode Episode { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users rating class.
|
||||
/// </summary>
|
||||
public class TraktEpisodeRated : TraktRated
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the episode.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episode")]
|
||||
public TraktEpisode Episode { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings;
|
||||
|
||||
public class TraktMovieRated : TraktRated
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings
|
||||
{
|
||||
[JsonPropertyName("movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users movie rated class.
|
||||
/// </summary>
|
||||
public class TraktMovieRated : TraktRated
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the movie.
|
||||
/// </summary>
|
||||
[JsonPropertyName("movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings;
|
||||
|
||||
public class TraktSeasonRated : TraktRated
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings
|
||||
{
|
||||
[JsonPropertyName("season")]
|
||||
public TraktSeason Season { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users season rated class.
|
||||
/// </summary>
|
||||
public class TraktSeasonRated : TraktRated
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the season.
|
||||
/// </summary>
|
||||
[JsonPropertyName("season")]
|
||||
public TraktSeason Season { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings;
|
||||
|
||||
public class TraktShowRated : TraktRated
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings
|
||||
{
|
||||
[JsonPropertyName("show")]
|
||||
public TraktShow Show { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users show rated class.
|
||||
/// </summary>
|
||||
public class TraktShowRated : TraktRated
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the show.
|
||||
/// </summary>
|
||||
[JsonPropertyName("show")]
|
||||
public TraktShow Show { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,28 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Watched;
|
||||
|
||||
public class Episode
|
||||
namespace Trakt.Api.DataContracts.Users.Watched
|
||||
{
|
||||
[JsonPropertyName("last_watched_at")]
|
||||
public string LastWatchedAt { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users episode watched class.
|
||||
/// </summary>
|
||||
public class Episode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the last watched date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("last_watched_at")]
|
||||
public string LastWatchedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episode number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[JsonPropertyName("plays")]
|
||||
public int Plays { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of plays.
|
||||
/// </summary>
|
||||
[JsonPropertyName("plays")]
|
||||
public int Plays { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,26 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Watched;
|
||||
|
||||
public class Season
|
||||
namespace Trakt.Api.DataContracts.Users.Watched
|
||||
{
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users season watched class.
|
||||
/// </summary>
|
||||
public class Season
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the season number.
|
||||
/// </summary>
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<Episode> Episodes { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the episodes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<Episode> Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,29 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Watched;
|
||||
|
||||
public class TraktMovieWatched
|
||||
namespace Trakt.Api.DataContracts.Users.Watched
|
||||
{
|
||||
[JsonPropertyName("plays")]
|
||||
public int Plays { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users movie watched class.
|
||||
/// </summary>
|
||||
public class TraktMovieWatched
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of plays.
|
||||
/// </summary>
|
||||
[JsonPropertyName("plays")]
|
||||
public int Plays { get; set; }
|
||||
|
||||
[JsonPropertyName("last_watched_at")]
|
||||
public string LastWatchedAt { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the last watched date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("last_watched_at")]
|
||||
public string LastWatchedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the movie.
|
||||
/// </summary>
|
||||
[JsonPropertyName("movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,45 @@
|
||||
#pragma warning disable CA2227
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA1002
|
||||
#pragma warning disable CA2227
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Watched;
|
||||
|
||||
public class TraktShowWatched
|
||||
namespace Trakt.Api.DataContracts.Users.Watched
|
||||
{
|
||||
[JsonPropertyName("plays")]
|
||||
public int Plays { get; set; }
|
||||
/// <summary>
|
||||
/// The trakt.tv users show watched class.
|
||||
/// </summary>
|
||||
public class TraktShowWatched
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of plays.
|
||||
/// </summary>
|
||||
[JsonPropertyName("plays")]
|
||||
public int Plays { get; set; }
|
||||
|
||||
[JsonPropertyName("reset_at")]
|
||||
public string ResetAt { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the reset date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("reset_at")]
|
||||
public string ResetAt { get; set; }
|
||||
|
||||
[JsonPropertyName("last_watched_at")]
|
||||
public string LastWatchedAt { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the last watched date.
|
||||
/// </summary>
|
||||
[JsonPropertyName("last_watched_at")]
|
||||
public string LastWatchedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("show")]
|
||||
public TraktShow Show { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the show.
|
||||
/// </summary>
|
||||
[JsonPropertyName("show")]
|
||||
public TraktShow Show { get; set; }
|
||||
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<Season> Seasons { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the seasons.
|
||||
/// </summary>
|
||||
[JsonPropertyName("seasons")]
|
||||
public List<Season> Seasons { get; set; }
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,145 +13,146 @@ using Trakt.Api.DataContracts.BaseModel;
|
||||
using Trakt.Api.DataContracts.Sync;
|
||||
using Trakt.Helpers;
|
||||
|
||||
namespace Trakt.Api;
|
||||
|
||||
/// <summary>
|
||||
/// The Trakt.tv controller.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
[Produces(MediaTypeNames.Application.Json)]
|
||||
public class TraktController : ControllerBase
|
||||
namespace Trakt.Api
|
||||
{
|
||||
private readonly TraktApi _traktApi;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger<TraktController> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TraktController"/> class.
|
||||
/// The trakt.tv controller class.
|
||||
/// </summary>
|
||||
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
|
||||
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||
/// <param name="httpClient">Instance of the <see cref="IHttpClient"/> interface.</param>
|
||||
/// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
|
||||
public TraktController(
|
||||
IUserDataManager userDataManager,
|
||||
ILoggerFactory loggerFactory,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IServerApplicationHost appHost,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager)
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
[Produces(MediaTypeNames.Application.Json)]
|
||||
public class TraktController : ControllerBase
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<TraktController>();
|
||||
_traktApi = new TraktApi(loggerFactory.CreateLogger<TraktApi>(), httpClientFactory, appHost, userDataManager, fileSystem);
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
private readonly TraktApi _traktApi;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger<TraktController> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Authorize this server with trakt.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id of the user connecting to trakt.</param>
|
||||
/// <response code="200">Authorization code requested successfully.</response>
|
||||
/// <returns>The trakt authorization code.</returns>
|
||||
[HttpPost("Users/{userId}/Authorize")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> TraktDeviceAuthorization([FromRoute] string userId)
|
||||
{
|
||||
_logger.LogInformation("TraktDeviceAuthorization request received");
|
||||
|
||||
// Create a user if we don't have one yet - TODO there should be an endpoint for this that creates a default user
|
||||
var traktUser = UserHelper.GetTraktUser(userId);
|
||||
if (traktUser == null)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TraktController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
|
||||
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||
/// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
|
||||
public TraktController(
|
||||
IUserDataManager userDataManager,
|
||||
ILoggerFactory loggerFactory,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IServerApplicationHost appHost,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
Plugin.Instance.PluginConfiguration.AddUser(userId);
|
||||
traktUser = UserHelper.GetTraktUser(userId);
|
||||
Plugin.Instance.SaveConfiguration();
|
||||
_logger = loggerFactory.CreateLogger<TraktController>();
|
||||
_traktApi = new TraktApi(loggerFactory.CreateLogger<TraktApi>(), httpClientFactory, appHost, userDataManager, fileSystem);
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
string userCode = await _traktApi.AuthorizeDevice(traktUser).ConfigureAwait(false);
|
||||
|
||||
return new
|
||||
/// <summary>
|
||||
/// Authorize this server with trakt.tv.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id of the user connecting to trakt.tv.</param>
|
||||
/// <response code="200">Authorization code requested successfully.</response>
|
||||
/// <returns>The trakt.tv authorization code.</returns>
|
||||
[HttpPost("Users/{userId}/Authorize")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> TraktDeviceAuthorization([FromRoute] string userId)
|
||||
{
|
||||
userCode
|
||||
};
|
||||
}
|
||||
_logger.LogInformation("TraktDeviceAuthorization request received");
|
||||
|
||||
/// <summary>
|
||||
/// Poll the trakt device authorization status
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <response code="200">Polling successful.</response>
|
||||
/// <returns>A value indicating whether the authorization code was connected to a trakt account.</returns>
|
||||
[HttpGet("Users/{userId}/PollAuthorizationStatus")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<object> TraktPollAuthorizationStatus([FromRoute] string userId)
|
||||
{
|
||||
_logger.LogInformation("TraktPollAuthorizationStatus request received");
|
||||
var traktUser = UserHelper.GetTraktUser(userId);
|
||||
bool isAuthorized = traktUser.AccessToken != null && traktUser.RefreshToken != null;
|
||||
// Create a user if we don't have one yet - TODO there should be an endpoint for this that creates a default user
|
||||
var traktUser = UserHelper.GetTraktUser(userId);
|
||||
if (traktUser == null)
|
||||
{
|
||||
Plugin.Instance.PluginConfiguration.AddUser(userId);
|
||||
traktUser = UserHelper.GetTraktUser(userId);
|
||||
Plugin.Instance.SaveConfiguration();
|
||||
}
|
||||
|
||||
if (Plugin.Instance.PollingTasks.TryGetValue(userId, out var task))
|
||||
{
|
||||
isAuthorized = task.Result;
|
||||
Plugin.Instance.PollingTasks.Remove(userId);
|
||||
string userCode = await _traktApi.AuthorizeDevice(traktUser).ConfigureAwait(false);
|
||||
|
||||
return new
|
||||
{
|
||||
userCode
|
||||
};
|
||||
}
|
||||
|
||||
return new
|
||||
/// <summary>
|
||||
/// Poll the trakt.tv device authorization status.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <response code="200">Polling successful.</response>
|
||||
/// <returns>A value indicating whether the authorization code was connected to a trakt.tv account.</returns>
|
||||
[HttpGet("Users/{userId}/PollAuthorizationStatus")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<object> TraktPollAuthorizationStatus([FromRoute] string userId)
|
||||
{
|
||||
isAuthorized
|
||||
};
|
||||
}
|
||||
_logger.LogInformation("TraktPollAuthorizationStatus request received");
|
||||
var traktUser = UserHelper.GetTraktUser(userId);
|
||||
bool isAuthorized = traktUser.AccessToken != null && traktUser.RefreshToken != null;
|
||||
|
||||
/// <summary>
|
||||
/// Rate an item.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="rating">Rating between 1 - 10 (0 = unrate).</param>
|
||||
/// <response code="200">Item rated successfully.</response>
|
||||
/// <returns>A <see cref="TraktSyncResponse"/>.</returns>
|
||||
[HttpPost("Users/{userId}/Items/{itemId}/Rate")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TraktSyncResponse>> TraktRateItem([FromRoute] string userId, [FromRoute] Guid itemId, [FromQuery] int rating)
|
||||
{
|
||||
_logger.LogInformation("RateItem request received");
|
||||
if (Plugin.Instance.PollingTasks.TryGetValue(userId, out var task))
|
||||
{
|
||||
isAuthorized = task.Result;
|
||||
Plugin.Instance.PollingTasks.Remove(userId);
|
||||
}
|
||||
|
||||
var currentItem = _libraryManager.GetItemById(itemId);
|
||||
|
||||
if (currentItem == null)
|
||||
{
|
||||
_logger.LogInformation("currentItem is null");
|
||||
return null;
|
||||
return new
|
||||
{
|
||||
isAuthorized
|
||||
};
|
||||
}
|
||||
|
||||
return await _traktApi.SendItemRating(currentItem, rating, UserHelper.GetTraktUser(userId)).ConfigureAwait(false);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rate an item.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="rating">Rating between 1 - 10 (0 = unrate).</param>
|
||||
/// <response code="200">Item rated successfully.</response>
|
||||
/// <returns>A <see cref="TraktSyncResponse"/>.</returns>
|
||||
[HttpPost("Users/{userId}/Items/{itemId}/Rate")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TraktSyncResponse>> TraktRateItem([FromRoute] string userId, [FromRoute] Guid itemId, [FromQuery] int rating)
|
||||
{
|
||||
_logger.LogInformation("RateItem request received");
|
||||
|
||||
/// <summary>
|
||||
/// Get recommended trakt movies.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <response code="200">Recommended movies returned.</response>
|
||||
/// <returns>A <see cref="List{TraktMovie}"/> with recommended movies.</returns>
|
||||
[HttpPost("Users/{userId}/RecommendedMovies")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<List<TraktMovie>>> RecommendedTraktMovies([FromRoute] string userId)
|
||||
{
|
||||
return await _traktApi.SendMovieRecommendationsRequest(UserHelper.GetTraktUser(userId)).ConfigureAwait(false);
|
||||
}
|
||||
var currentItem = _libraryManager.GetItemById(itemId);
|
||||
|
||||
/// <summary>
|
||||
/// Get recommended trakt shows.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <response code="200">Recommended shows returned.</response>
|
||||
/// <returns>A <see cref="List{TraktShow}"/> with recommended movies.</returns>
|
||||
[HttpPost("Users/{userId}/RecommendedShows")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<List<TraktShow>>> RecommendedTraktShows([FromRoute] string userId)
|
||||
{
|
||||
return await _traktApi.SendShowRecommendationsRequest(UserHelper.GetTraktUser(userId)).ConfigureAwait(false);
|
||||
if (currentItem == null)
|
||||
{
|
||||
_logger.LogInformation("currentItem is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
return await _traktApi.SendItemRating(currentItem, rating, UserHelper.GetTraktUser(userId)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get recommended trakt.tv movies.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <response code="200">Recommended movies returned.</response>
|
||||
/// <returns>A <see cref="List{TraktMovie}"/> with recommended movies.</returns>
|
||||
[HttpPost("Users/{userId}/RecommendedMovies")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<List<TraktMovie>>> RecommendedTraktMovies([FromRoute] string userId)
|
||||
{
|
||||
return await _traktApi.SendMovieRecommendationsRequest(UserHelper.GetTraktUser(userId)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get recommended trakt.tv shows.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <response code="200">Recommended shows returned.</response>
|
||||
/// <returns>A <see cref="List{TraktShow}"/> with recommended movies.</returns>
|
||||
[HttpPost("Users/{userId}/RecommendedShows")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<List<TraktShow>>> RecommendedTraktShows([FromRoute] string userId)
|
||||
{
|
||||
return await _traktApi.SendShowRecommendationsRequest(UserHelper.GetTraktUser(userId)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,118 @@
|
||||
namespace Trakt.Api;
|
||||
|
||||
public static class TraktUris
|
||||
namespace Trakt.Api
|
||||
{
|
||||
public const string BaseUrl = "https://api.trakt.tv";
|
||||
public const string ClientId = "58f2251f1c9e7275e94fef723a8604e6848bbf86a0d97dda82382a6c3231608c";
|
||||
public const string ClientSecret = "bf9fce37cf45c1de91da009e7ac6fca905a35d7a718bf65a52f92199073a2503";
|
||||
/// <summary>
|
||||
/// The trakt.tv URI class.
|
||||
/// </summary>
|
||||
public static class TraktUris
|
||||
{
|
||||
/// <summary>
|
||||
/// The base URL.
|
||||
/// </summary>
|
||||
public const string BaseUrl = "https://api.trakt.tv";
|
||||
|
||||
public const string DeviceCode = BaseUrl + "/oauth/device/code";
|
||||
public const string DeviceToken = BaseUrl + "/oauth/device/token";
|
||||
public const string AccessToken = BaseUrl + "/oauth/token";
|
||||
/// <summary>
|
||||
/// The client id.
|
||||
/// </summary>
|
||||
public const string ClientId = "58f2251f1c9e7275e94fef723a8604e6848bbf86a0d97dda82382a6c3231608c";
|
||||
|
||||
public const string SyncCollectionAdd = BaseUrl + "/sync/collection";
|
||||
public const string SyncCollectionRemove = BaseUrl + "/sync/collection/remove";
|
||||
public const string SyncWatchedHistoryAdd = BaseUrl + "/sync/history";
|
||||
public const string SyncWatchedHistoryRemove = BaseUrl + "/sync/history/remove";
|
||||
public const string SyncRatingsAdd = BaseUrl + "/sync/ratings";
|
||||
/// <summary>
|
||||
/// The client secret.
|
||||
/// </summary>
|
||||
public const string ClientSecret = "bf9fce37cf45c1de91da009e7ac6fca905a35d7a718bf65a52f92199073a2503";
|
||||
|
||||
public const string ScrobbleStart = BaseUrl + "/scrobble/start";
|
||||
public const string ScrobblePause = BaseUrl + "/scrobble/pause";
|
||||
public const string ScrobbleStop = BaseUrl + "/scrobble/stop";
|
||||
/// <summary>
|
||||
/// The device code URI.
|
||||
/// </summary>
|
||||
public const string DeviceCode = BaseUrl + "/oauth/device/code";
|
||||
|
||||
public const string WatchedMovies = BaseUrl + "/sync/watched/movies";
|
||||
public const string WatchedShows = BaseUrl + "/sync/watched/shows";
|
||||
public const string CollectedMovies = BaseUrl + "/sync/collection/movies?extended=metadata";
|
||||
public const string CollectedShows = BaseUrl + "/sync/collection/shows?extended=metadata";
|
||||
/// <summary>
|
||||
/// The device token URI.
|
||||
/// </summary>
|
||||
public const string DeviceToken = BaseUrl + "/oauth/device/token";
|
||||
|
||||
// Recommendations
|
||||
public const string RecommendationsMovies = BaseUrl + "/recommendations/movies";
|
||||
public const string RecommendationsShows = BaseUrl + "/recommendations/shows";
|
||||
/// <summary>
|
||||
/// The access token URI.
|
||||
/// </summary>
|
||||
public const string AccessToken = BaseUrl + "/oauth/token";
|
||||
|
||||
// Recommendations
|
||||
public const string RecommendationsMoviesDismiss = BaseUrl + "/recommendations/movies/{0}";
|
||||
public const string RecommendationsShowsDismiss = BaseUrl + "/recommendations/shows/{0}";
|
||||
/// <summary>
|
||||
/// The collection sync add URI.
|
||||
/// </summary>
|
||||
public const string SyncCollectionAdd = BaseUrl + "/sync/collection";
|
||||
|
||||
/// <summary>
|
||||
/// The collection sync remove URI.
|
||||
/// </summary>
|
||||
public const string SyncCollectionRemove = BaseUrl + "/sync/collection/remove";
|
||||
|
||||
/// <summary>
|
||||
/// The watched history add URI.
|
||||
/// </summary>
|
||||
public const string SyncWatchedHistoryAdd = BaseUrl + "/sync/history";
|
||||
|
||||
/// <summary>
|
||||
/// The watched history remove URI.
|
||||
/// </summary>
|
||||
public const string SyncWatchedHistoryRemove = BaseUrl + "/sync/history/remove";
|
||||
|
||||
/// <summary>
|
||||
/// The ratings add URI.
|
||||
/// </summary>
|
||||
public const string SyncRatingsAdd = BaseUrl + "/sync/ratings";
|
||||
|
||||
/// <summary>
|
||||
/// The scrobble start URI.
|
||||
/// </summary>
|
||||
public const string ScrobbleStart = BaseUrl + "/scrobble/start";
|
||||
|
||||
/// <summary>
|
||||
/// The scrobble pause URI.
|
||||
/// </summary>
|
||||
public const string ScrobblePause = BaseUrl + "/scrobble/pause";
|
||||
|
||||
/// <summary>
|
||||
/// The scrobble stop URI.
|
||||
/// </summary>
|
||||
public const string ScrobbleStop = BaseUrl + "/scrobble/stop";
|
||||
|
||||
/// <summary>
|
||||
/// The watched movies URI.
|
||||
/// </summary>
|
||||
public const string WatchedMovies = BaseUrl + "/sync/watched/movies";
|
||||
|
||||
/// <summary>
|
||||
/// The watched shows URI.
|
||||
/// </summary>
|
||||
public const string WatchedShows = BaseUrl + "/sync/watched/shows";
|
||||
|
||||
/// <summary>
|
||||
/// The collected movies URI.
|
||||
/// </summary>
|
||||
public const string CollectedMovies = BaseUrl + "/sync/collection/movies?extended=metadata";
|
||||
|
||||
/// <summary>
|
||||
/// The collected series URI.
|
||||
/// </summary>
|
||||
public const string CollectedShows = BaseUrl + "/sync/collection/shows?extended=metadata";
|
||||
|
||||
/// <summary>
|
||||
/// The movies recommendations URI.
|
||||
/// </summary>
|
||||
public const string RecommendationsMovies = BaseUrl + "/recommendations/movies";
|
||||
|
||||
/// <summary>
|
||||
/// The shows recommendations URI.
|
||||
/// </summary>
|
||||
public const string RecommendationsShows = BaseUrl + "/recommendations/shows";
|
||||
|
||||
/// <summary>
|
||||
/// The movies recommendations dismiss URI.
|
||||
/// </summary>
|
||||
public const string RecommendationsMoviesDismiss = BaseUrl + "/recommendations/movies/{0}";
|
||||
|
||||
/// <summary>
|
||||
/// The shows recommendations dismiss URI.
|
||||
/// </summary>
|
||||
public const string RecommendationsShowsDismiss = BaseUrl + "/recommendations/shows/{0}";
|
||||
}
|
||||
}
|
||||
|
@ -5,25 +5,39 @@ using System.Linq;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
using Trakt.Model;
|
||||
|
||||
namespace Trakt.Configuration;
|
||||
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
namespace Trakt.Configuration
|
||||
{
|
||||
public PluginConfiguration()
|
||||
/// <summary>
|
||||
/// Plugin configuration class for trackt.tv plugin.
|
||||
/// </summary>
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
TraktUsers = Array.Empty<TraktUser>();
|
||||
}
|
||||
|
||||
public TraktUser[] TraktUsers { get; set; }
|
||||
|
||||
public void AddUser(string userId)
|
||||
{
|
||||
var traktUsers = TraktUsers.ToList();
|
||||
var traktUser = new TraktUser
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginConfiguration"/> class.
|
||||
/// </summary>
|
||||
public PluginConfiguration()
|
||||
{
|
||||
LinkedMbUserId = userId
|
||||
};
|
||||
traktUsers.Add(traktUser);
|
||||
TraktUsers = traktUsers.ToArray();
|
||||
TraktUsers = Array.Empty<TraktUser>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the trakt users.
|
||||
/// </summary>
|
||||
public TraktUser[] TraktUsers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a user to the trakt user.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
public void AddUser(string userId)
|
||||
{
|
||||
var traktUsers = TraktUsers.ToList();
|
||||
var traktUser = new TraktUser
|
||||
{
|
||||
LinkedMbUserId = userId
|
||||
};
|
||||
traktUsers.Add(traktUser);
|
||||
TraktUsers = traktUsers.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,193 +7,239 @@ using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Trakt.Api.DataContracts.Users.Collection;
|
||||
using Trakt.Helpers;
|
||||
using Trakt.Model;
|
||||
|
||||
namespace Trakt;
|
||||
|
||||
public static class Extensions
|
||||
namespace Trakt
|
||||
{
|
||||
public static int? ConvertToInt(this string input)
|
||||
/// <summary>
|
||||
/// Class for trakt.tv plugin extension functions.
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
if (int.TryParse(input, out int result))
|
||||
/// <summary>
|
||||
/// Convert string to int.
|
||||
/// </summary>
|
||||
/// <param name="input">String to convert to int.</param>
|
||||
/// <returns>int?.</returns>
|
||||
public static int? ConvertToInt(this string input)
|
||||
{
|
||||
return result;
|
||||
if (int.TryParse(input, out int result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
/// <summary>
|
||||
/// Checks if <see cref="TraktMetadata"/> is empty.
|
||||
/// </summary>
|
||||
/// <param name="metadata">String to convert to int.</param>
|
||||
/// <returns><see cref="bool"/> indicating if the provided <see cref="TraktMetadata"/> is empty.</returns>
|
||||
public static bool IsEmpty(this TraktMetadata metadata)
|
||||
=> string.IsNullOrEmpty(metadata.MediaType)
|
||||
&& string.IsNullOrEmpty(metadata.Resolution)
|
||||
&& string.IsNullOrEmpty(metadata.Audio)
|
||||
&& string.IsNullOrEmpty(metadata.AudioChannels);
|
||||
|
||||
public static bool IsEmpty(this TraktMetadata metadata)
|
||||
=> string.IsNullOrEmpty(metadata.MediaType)
|
||||
&& string.IsNullOrEmpty(metadata.Resolution)
|
||||
&& string.IsNullOrEmpty(metadata.Audio)
|
||||
&& string.IsNullOrEmpty(metadata.AudioChannels);
|
||||
|
||||
public static string GetCodecRepresetation(this MediaStream audioStream)
|
||||
{
|
||||
var audio = audioStream != null && !string.IsNullOrEmpty(audioStream.Codec)
|
||||
? audioStream.Codec.ToLowerInvariant().Replace(' ', '_')
|
||||
: null;
|
||||
switch (audio)
|
||||
/// <summary>
|
||||
/// Gets the trakt.tv codec representation of a <see cref="MediaStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="audioStream">The <see cref="MediaStream"/>.</param>
|
||||
/// <returns>string.</returns>
|
||||
public static string GetCodecRepresetation(this MediaStream audioStream)
|
||||
{
|
||||
case "truehd":
|
||||
return TraktAudio.dolby_truehd.ToString();
|
||||
case "dts":
|
||||
case "dca":
|
||||
return TraktAudio.dts.ToString();
|
||||
case "dtshd":
|
||||
return TraktAudio.dts_ma.ToString();
|
||||
case "ac3":
|
||||
return TraktAudio.dolby_digital.ToString();
|
||||
case "aac":
|
||||
return TraktAudio.aac.ToString();
|
||||
case "mp2":
|
||||
return TraktAudio.mp3.ToString();
|
||||
case "pcm":
|
||||
return TraktAudio.lpcm.ToString();
|
||||
case "ogg":
|
||||
return TraktAudio.ogg.ToString();
|
||||
case "wma":
|
||||
return TraktAudio.wma.ToString();
|
||||
case "flac":
|
||||
return TraktAudio.flac.ToString();
|
||||
default:
|
||||
var audio = audioStream != null && !string.IsNullOrEmpty(audioStream.Codec)
|
||||
? audioStream.Codec.ToLowerInvariant().Replace(' ', '_')
|
||||
: null;
|
||||
switch (audio)
|
||||
{
|
||||
case "truehd":
|
||||
return TraktAudio.DolbyDigital.ToString();
|
||||
case "dts":
|
||||
case "dca":
|
||||
return TraktAudio.Dts.ToString();
|
||||
case "dtshd":
|
||||
return TraktAudio.DtsMa.ToString();
|
||||
case "ac3":
|
||||
return TraktAudio.DolbyDigital.ToString();
|
||||
case "aac":
|
||||
return TraktAudio.Aac.ToString();
|
||||
case "mp2":
|
||||
return TraktAudio.Mp3.ToString();
|
||||
case "pcm":
|
||||
return TraktAudio.Lpcm.ToString();
|
||||
case "ogg":
|
||||
return TraktAudio.Ogg.ToString();
|
||||
case "wma":
|
||||
return TraktAudio.Wma.ToString();
|
||||
case "flac":
|
||||
return TraktAudio.Flac.ToString();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if metadata of new collected movie is different from the already collected.
|
||||
/// </summary>
|
||||
/// <param name="collectedMovie">The <see cref="TraktMovieCollected"/>.</param>
|
||||
/// <param name="movie">The <see cref="Movie"/>.</param>
|
||||
/// <returns><see cref="bool"/> indicating if the new movie has different metadata to the already collected.</returns>
|
||||
public static bool MetadataIsDifferent(this TraktMovieCollected collectedMovie, Movie movie)
|
||||
{
|
||||
var audioStream = movie.GetMediaStreams().FirstOrDefault(x => x.Type == MediaStreamType.Audio);
|
||||
|
||||
var resolution = movie.GetDefaultVideoStream().GetResolution();
|
||||
var audio = GetCodecRepresetation(audioStream);
|
||||
var audioChannels = audioStream.GetAudioChannels();
|
||||
|
||||
if (collectedMovie.Metadata == null || collectedMovie.Metadata.IsEmpty())
|
||||
{
|
||||
return !string.IsNullOrEmpty(resolution)
|
||||
|| !string.IsNullOrEmpty(audio)
|
||||
|| !string.IsNullOrEmpty(audioChannels);
|
||||
}
|
||||
|
||||
return collectedMovie.Metadata.Audio != audio
|
||||
|| collectedMovie.Metadata.AudioChannels != audioChannels
|
||||
|| collectedMovie.Metadata.Resolution != resolution;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the resolution of a <see cref="MediaStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="videoStream">The <see cref="MediaStream"/>.</param>
|
||||
/// <returns>string.</returns>
|
||||
public static string GetResolution(this MediaStream videoStream)
|
||||
{
|
||||
if (videoStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool MetadataIsDifferent(this TraktMovieCollected collectedMovie, Movie movie)
|
||||
{
|
||||
var audioStream = movie.GetMediaStreams().FirstOrDefault(x => x.Type == MediaStreamType.Audio);
|
||||
if (!videoStream.Width.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var resolution = movie.GetDefaultVideoStream().GetResolution();
|
||||
var audio = GetCodecRepresetation(audioStream);
|
||||
var audioChannels = audioStream.GetAudioChannels();
|
||||
if (videoStream.Width.Value >= 3800)
|
||||
{
|
||||
return "uhd_4k";
|
||||
}
|
||||
|
||||
if (collectedMovie.Metadata == null || collectedMovie.Metadata.IsEmpty())
|
||||
{
|
||||
return !string.IsNullOrEmpty(resolution)
|
||||
|| !string.IsNullOrEmpty(audio)
|
||||
|| !string.IsNullOrEmpty(audioChannels);
|
||||
}
|
||||
if (videoStream.Width.Value >= 1900)
|
||||
{
|
||||
return "hd_1080p";
|
||||
}
|
||||
|
||||
return collectedMovie.Metadata.Audio != audio
|
||||
|| collectedMovie.Metadata.AudioChannels != audioChannels
|
||||
|| collectedMovie.Metadata.Resolution != resolution;
|
||||
}
|
||||
if (videoStream.Width.Value >= 1270)
|
||||
{
|
||||
return "hd_720p";
|
||||
}
|
||||
|
||||
if (videoStream.Width.Value >= 700)
|
||||
{
|
||||
return "sd_480p";
|
||||
}
|
||||
|
||||
public static string GetResolution(this MediaStream videoStream)
|
||||
{
|
||||
if (videoStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!videoStream.Width.HasValue)
|
||||
/// <summary>
|
||||
/// Gets the ISO-8620 representation of a <see cref="DateTime"/>.
|
||||
/// </summary>
|
||||
/// <param name="dt">The <see cref="DateTime"/>.</param>
|
||||
/// <returns>string.</returns>
|
||||
public static string ToISO8601(this DateTime dt)
|
||||
=> dt.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the season number of an <see cref="Episode"/>.
|
||||
/// </summary>
|
||||
/// <param name="episode">The <see cref="Episode"/>.</param>
|
||||
/// <returns>int.</returns>
|
||||
public static int GetSeasonNumber(this Episode episode)
|
||||
=> (episode.ParentIndexNumber != 0 ? episode.ParentIndexNumber ?? 1 : episode.ParentIndexNumber).Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of audio channels of a <see cref="MediaStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="audioStream">The <see cref="MediaStream"/>.</param>
|
||||
/// <returns>string.</returns>
|
||||
public static string GetAudioChannels(this MediaStream audioStream)
|
||||
{
|
||||
return null;
|
||||
if (audioStream == null || string.IsNullOrEmpty(audioStream.ChannelLayout))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var channels = audioStream.ChannelLayout.Split('(')[0];
|
||||
switch (channels)
|
||||
{
|
||||
case "7":
|
||||
return "6.1";
|
||||
case "6":
|
||||
return "5.1";
|
||||
case "5":
|
||||
return "5.0";
|
||||
case "4":
|
||||
return "4.0";
|
||||
case "3":
|
||||
return "2.1";
|
||||
case "stereo":
|
||||
return "2.0";
|
||||
case "mono":
|
||||
return "1.0";
|
||||
default:
|
||||
return channels;
|
||||
}
|
||||
}
|
||||
|
||||
if (videoStream.Width.Value >= 3800)
|
||||
/// <summary>
|
||||
/// Transforms an enumerable into a list with a speciifc amount of chunks.
|
||||
/// </summary>
|
||||
/// <param name="enumerable">The IEnumberable{T}.</param>
|
||||
/// <param name="chunkSize">Size of the Chunks.</param>
|
||||
/// <returns>IList{IEnumerable{T}}.</returns>
|
||||
/// <typeparam name="T">The type of IEnumerable.</typeparam>
|
||||
public static IList<IEnumerable<T>> ToChunks<T>(this IEnumerable<T> enumerable, int chunkSize)
|
||||
{
|
||||
return "uhd_4k";
|
||||
var itemsReturned = 0;
|
||||
var list = enumerable.ToList(); // Prevent multiple execution of IEnumerable.
|
||||
var count = list.Count;
|
||||
var chunks = new List<IEnumerable<T>>();
|
||||
while (itemsReturned < count)
|
||||
{
|
||||
chunks.Add(list.Take(chunkSize).ToList());
|
||||
list = list.Skip(chunkSize).ToList();
|
||||
itemsReturned += chunkSize;
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
if (videoStream.Width.Value >= 1900)
|
||||
/// <summary>
|
||||
/// Splits a progress into multiple parts.
|
||||
/// </summary>
|
||||
/// <param name="parent">The progress.</param>
|
||||
/// <param name="parts">The number of parts to split into.</param>
|
||||
/// <returns>ISplittableProgress{double}.</returns>
|
||||
public static ISplittableProgress<double> Split(this IProgress<double> parent, int parts)
|
||||
{
|
||||
return "hd_1080p";
|
||||
var current = parent.ToSplittableProgress();
|
||||
return current.Split(parts);
|
||||
}
|
||||
|
||||
if (videoStream.Width.Value >= 1270)
|
||||
/// <summary>
|
||||
/// Converts a progress into a splittable progress.
|
||||
/// </summary>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <returns>ISplittableProgress{double}.</returns>
|
||||
public static ISplittableProgress<double> ToSplittableProgress(this IProgress<double> progress)
|
||||
{
|
||||
return "hd_720p";
|
||||
}
|
||||
|
||||
if (videoStream.Width.Value >= 700)
|
||||
{
|
||||
return "sd_480p";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string ToISO8601(this DateTime dt)
|
||||
=> dt.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture);
|
||||
|
||||
public static int GetSeasonNumber(this Episode episode)
|
||||
=> (episode.ParentIndexNumber != 0 ? episode.ParentIndexNumber ?? 1 : episode.ParentIndexNumber).Value;
|
||||
|
||||
public static string GetAudioChannels(this MediaStream audioStream)
|
||||
{
|
||||
if (audioStream == null || string.IsNullOrEmpty(audioStream.ChannelLayout))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var channels = audioStream.ChannelLayout.Split('(')[0];
|
||||
switch (channels)
|
||||
{
|
||||
case "7":
|
||||
return "6.1";
|
||||
case "6":
|
||||
return "5.1";
|
||||
case "5":
|
||||
return "5.0";
|
||||
case "4":
|
||||
return "4.0";
|
||||
case "3":
|
||||
return "2.1";
|
||||
case "stereo":
|
||||
return "2.0";
|
||||
case "mono":
|
||||
return "1.0";
|
||||
default:
|
||||
return channels;
|
||||
var splittable = new SplittableProgress(progress.Report);
|
||||
return splittable;
|
||||
}
|
||||
}
|
||||
|
||||
public static IList<IEnumerable<T>> ToChunks<T>(this IEnumerable<T> enumerable, int chunkSize)
|
||||
{
|
||||
var itemsReturned = 0;
|
||||
var list = enumerable.ToList(); // Prevent multiple execution of IEnumerable.
|
||||
var count = list.Count;
|
||||
var chunks = new List<IEnumerable<T>>();
|
||||
while (itemsReturned < count)
|
||||
{
|
||||
chunks.Add(list.Take(chunkSize).ToList());
|
||||
list = list.Skip(chunkSize).ToList();
|
||||
itemsReturned += chunkSize;
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
public static ISplittableProgress<double> Split(this IProgress<double> parent, int parts)
|
||||
{
|
||||
var current = parent.ToSplittableProgress();
|
||||
return current.Split(parts);
|
||||
}
|
||||
|
||||
public static ISplittableProgress<double> ToSplittableProgress(this IProgress<double> progress)
|
||||
{
|
||||
var splittable = new SplittableProgress(progress.Report);
|
||||
return splittable;
|
||||
}
|
||||
|
||||
#pragma warning disable
|
||||
public enum TraktAudio
|
||||
{
|
||||
lpcm,
|
||||
mp3,
|
||||
aac,
|
||||
dts,
|
||||
dts_ma,
|
||||
flac,
|
||||
ogg,
|
||||
wma,
|
||||
dolby_prologic,
|
||||
dolby_digital,
|
||||
dolby_digital_plus,
|
||||
dolby_truehd
|
||||
}
|
||||
#pragma warning restore CA1707
|
||||
}
|
||||
|
@ -1,8 +1,23 @@
|
||||
namespace Trakt.Helpers;
|
||||
|
||||
public enum EventType
|
||||
namespace Trakt.Helpers
|
||||
{
|
||||
Add,
|
||||
Remove,
|
||||
Update
|
||||
/// <summary>
|
||||
/// Enum EventType.
|
||||
/// </summary>
|
||||
public enum EventType
|
||||
{
|
||||
/// <summary>
|
||||
/// The addevent.
|
||||
/// </summary>
|
||||
Add,
|
||||
|
||||
/// <summary>
|
||||
/// The remove event.
|
||||
/// </summary>
|
||||
Remove,
|
||||
|
||||
/// <summary>
|
||||
/// The update event.
|
||||
/// </summary>
|
||||
Update
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,18 @@
|
||||
namespace Trakt.Helpers;
|
||||
using System;
|
||||
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Similar to <see cref="IProgress{T}"/>, but it contains a split method and Report is relative, not absolute.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of progress update value</typeparam>
|
||||
public interface ISplittableProgress<T> : IProgress<T>
|
||||
namespace Trakt.Helpers
|
||||
{
|
||||
ISplittableProgress<T> Split(int parts);
|
||||
/// <summary>
|
||||
/// Similar to <see cref="IProgress{T}"/>, but it contains a split method and Report is relative, not absolute.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of progress update value.</typeparam>
|
||||
public interface ISplittableProgress<T> : IProgress<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Splits the progress into parts.
|
||||
/// </summary>
|
||||
/// <param name="parts">The amount of parts to split into.</param>
|
||||
/// <returns>ISplittableProgress{T}.</returns>
|
||||
ISplittableProgress<T> Split(int parts);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using Trakt.Model;
|
||||
|
||||
namespace Trakt.Helpers;
|
||||
|
||||
internal class LibraryEvent
|
||||
namespace Trakt.Helpers
|
||||
{
|
||||
public BaseItem Item { get; set; }
|
||||
internal class LibraryEvent
|
||||
{
|
||||
public BaseItem Item { get; set; }
|
||||
|
||||
public TraktUser TraktUser { get; set; }
|
||||
public TraktUser TraktUser { get; set; }
|
||||
|
||||
public EventType EventType { get; set; }
|
||||
public EventType EventType { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -11,283 +11,286 @@ using Microsoft.Extensions.Logging;
|
||||
using Trakt.Api;
|
||||
using Trakt.Model;
|
||||
|
||||
namespace Trakt.Helpers;
|
||||
|
||||
internal class LibraryManagerEventsHelper : IDisposable
|
||||
namespace Trakt.Helpers
|
||||
{
|
||||
private readonly List<LibraryEvent> _queuedEvents;
|
||||
private readonly ILogger<LibraryManagerEventsHelper> _logger;
|
||||
private readonly TraktApi _traktApi;
|
||||
private Timer _queueTimer;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="traktApi"></param>
|
||||
public LibraryManagerEventsHelper(ILogger<LibraryManagerEventsHelper> logger, TraktApi traktApi)
|
||||
internal class LibraryManagerEventsHelper : IDisposable
|
||||
{
|
||||
_queuedEvents = new List<LibraryEvent>();
|
||||
_logger = logger;
|
||||
_traktApi = traktApi;
|
||||
}
|
||||
private readonly List<LibraryEvent> _queuedEvents;
|
||||
private readonly ILogger<LibraryManagerEventsHelper> _logger;
|
||||
private readonly TraktApi _traktApi;
|
||||
private Timer _queueTimer;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="eventType"></param>
|
||||
public void QueueItem(BaseItem item, EventType eventType)
|
||||
{
|
||||
if (item == null)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibraryManagerEventsHelper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="traktApi">The <see cref="TraktApi"/>.</param>
|
||||
public LibraryManagerEventsHelper(ILogger<LibraryManagerEventsHelper> logger, TraktApi traktApi)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
_queuedEvents = new List<LibraryEvent>();
|
||||
_logger = logger;
|
||||
_traktApi = traktApi;
|
||||
}
|
||||
|
||||
if (_queueTimer == null)
|
||||
/// <summary>
|
||||
/// Queues an item to be added to trakt.
|
||||
/// </summary>
|
||||
/// <param name="item"> The <see cref="BaseItem"/>.</param>
|
||||
/// <param name="eventType">The <see cref="EventType"/>.</param>
|
||||
public void QueueItem(BaseItem item, EventType eventType)
|
||||
{
|
||||
_queueTimer = new Timer(
|
||||
OnQueueTimerCallback,
|
||||
null,
|
||||
TimeSpan.FromMilliseconds(20000),
|
||||
Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_queueTimer.Change(TimeSpan.FromMilliseconds(20000), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
var users = Plugin.Instance.PluginConfiguration.TraktUsers;
|
||||
|
||||
if (users == null || users.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// we need to process the video for each user
|
||||
foreach (var user in users.Where(x => _traktApi.CanSync(item, x)))
|
||||
{
|
||||
// we have a match, this user is watching the folder the video is in. Add to queue and they
|
||||
// will be processed when the next timer elapsed event fires.
|
||||
var libraryEvent = new LibraryEvent { Item = item, TraktUser = user, EventType = eventType };
|
||||
_queuedEvents.Add(libraryEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private async void OnQueueTimerCallback(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
await OnQueueTimerCallbackInternal().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in OnQueueTimerCallbackInternal");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnQueueTimerCallbackInternal()
|
||||
{
|
||||
_logger.LogInformation("Timer elapsed - Processing queued items");
|
||||
|
||||
if (!_queuedEvents.Any())
|
||||
{
|
||||
_logger.LogInformation("No events... Stopping queue timer");
|
||||
// This may need to go
|
||||
return;
|
||||
}
|
||||
|
||||
var queue = _queuedEvents.ToList();
|
||||
_queuedEvents.Clear();
|
||||
foreach (var traktUser in Plugin.Instance.PluginConfiguration.TraktUsers)
|
||||
{
|
||||
var queuedMovieDeletes = queue.Where(ev =>
|
||||
new Guid(ev.TraktUser.LinkedMbUserId).Equals(new Guid(traktUser.LinkedMbUserId)) &&
|
||||
ev.Item is Movie &&
|
||||
ev.EventType == EventType.Remove).ToList();
|
||||
|
||||
if (queuedMovieDeletes.Any())
|
||||
if (item == null)
|
||||
{
|
||||
_logger.LogInformation("{Count} Movie Deletes to Process", queuedMovieDeletes.Count);
|
||||
await ProcessQueuedMovieEvents(queuedMovieDeletes, traktUser, EventType.Remove).ConfigureAwait(false);
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
if (_queueTimer == null)
|
||||
{
|
||||
_queueTimer = new Timer(
|
||||
OnQueueTimerCallback,
|
||||
null,
|
||||
TimeSpan.FromMilliseconds(20000),
|
||||
Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No Movie Deletes to Process");
|
||||
_queueTimer.Change(TimeSpan.FromMilliseconds(20000), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
var queuedMovieAdds = queue.Where(ev =>
|
||||
new Guid(ev.TraktUser.LinkedMbUserId).Equals(new Guid(traktUser.LinkedMbUserId)) &&
|
||||
ev.Item is Movie &&
|
||||
ev.EventType == EventType.Add).ToList();
|
||||
var users = Plugin.Instance.PluginConfiguration.TraktUsers;
|
||||
|
||||
if (queuedMovieAdds.Any())
|
||||
if (users == null || users.Length == 0)
|
||||
{
|
||||
_logger.LogInformation("{Count} Movie Adds to Process", queuedMovieAdds.Count);
|
||||
await ProcessQueuedMovieEvents(queuedMovieAdds, traktUser, EventType.Add).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No Movie Adds to Process");
|
||||
return;
|
||||
}
|
||||
|
||||
var queuedEpisodeDeletes = queue.Where(ev =>
|
||||
new Guid(ev.TraktUser.LinkedMbUserId).Equals(new Guid(traktUser.LinkedMbUserId)) &&
|
||||
ev.Item is Episode &&
|
||||
ev.EventType == EventType.Remove).ToList();
|
||||
|
||||
if (queuedEpisodeDeletes.Any())
|
||||
// Check if item can be synced for all users.
|
||||
foreach (var user in users.Where(user => _traktApi.CanSync(item, user)))
|
||||
{
|
||||
_logger.LogInformation("{Count} Episode Deletes to Process", queuedEpisodeDeletes.Count);
|
||||
await ProcessQueuedEpisodeEvents(queuedEpisodeDeletes, traktUser, EventType.Remove).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No Episode Deletes to Process");
|
||||
}
|
||||
|
||||
var queuedEpisodeAdds = queue.Where(ev =>
|
||||
new Guid(ev.TraktUser.LinkedMbUserId).Equals(new Guid(traktUser.LinkedMbUserId)) &&
|
||||
ev.Item is Episode &&
|
||||
ev.EventType == EventType.Add).ToList();
|
||||
|
||||
if (queuedEpisodeAdds.Any())
|
||||
{
|
||||
_logger.LogInformation("{Count} Episode Adds to Process", queuedEpisodeAdds.Count);
|
||||
await ProcessQueuedEpisodeEvents(queuedEpisodeAdds, traktUser, EventType.Add).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No Episode Adds to Process");
|
||||
}
|
||||
|
||||
var queuedShowDeletes = queue.Where(ev =>
|
||||
new Guid(ev.TraktUser.LinkedMbUserId).Equals(new Guid(traktUser.LinkedMbUserId)) &&
|
||||
ev.Item is Series &&
|
||||
ev.EventType == EventType.Remove).ToList();
|
||||
|
||||
if (queuedShowDeletes.Any())
|
||||
{
|
||||
_logger.LogInformation("{Count} Series Deletes to Process", queuedMovieDeletes.Count);
|
||||
await ProcessQueuedShowEvents(queuedShowDeletes, traktUser, EventType.Remove).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No Series Deletes to Process");
|
||||
// Add to queue.
|
||||
// Sync will be processed when the next timer elapsed event fires.
|
||||
_queuedEvents.Add(new LibraryEvent { Item = item, TraktUser = user, EventType = eventType });
|
||||
}
|
||||
}
|
||||
|
||||
// Everything is processed. Reset the event list.
|
||||
_queuedEvents.Clear();
|
||||
}
|
||||
|
||||
private async Task ProcessQueuedShowEvents(IEnumerable<LibraryEvent> events, TraktUser traktUser, EventType eventType)
|
||||
{
|
||||
var shows = events.Select(lev => (Series)lev.Item)
|
||||
.Where(lev => !string.IsNullOrEmpty(lev.Name) && !string.IsNullOrEmpty(lev.GetProviderId(MetadataProvider.Tvdb)))
|
||||
.ToList();
|
||||
try
|
||||
{
|
||||
// Should probably not be awaiting this, but it's unlikely a user will be deleting more than one or two shows at a time
|
||||
foreach (var show in shows)
|
||||
{
|
||||
await _traktApi.SendLibraryUpdateAsync(show, traktUser, eventType, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception handled processing queued series events");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="events"></param>
|
||||
/// <param name="traktUser"></param>
|
||||
/// <param name="eventType"></param>
|
||||
/// <returns></returns>
|
||||
private async Task ProcessQueuedMovieEvents(IEnumerable<LibraryEvent> events, TraktUser traktUser, EventType eventType)
|
||||
{
|
||||
var movies = events.Select(lev => (Movie)lev.Item)
|
||||
.Where(lev => !string.IsNullOrEmpty(lev.Name) && !string.IsNullOrEmpty(lev.GetProviderId(MetadataProvider.Imdb)))
|
||||
.ToList();
|
||||
try
|
||||
{
|
||||
await _traktApi.SendLibraryUpdateAsync(movies, traktUser, eventType, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception handled processing queued movie events");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="events"></param>
|
||||
/// <param name="traktUser"></param>
|
||||
/// <param name="eventType"></param>
|
||||
/// <returns></returns>
|
||||
private async Task ProcessQueuedEpisodeEvents(IEnumerable<LibraryEvent> events, TraktUser traktUser, EventType eventType)
|
||||
{
|
||||
var episodes = events.Select(lev => (Episode)lev.Item)
|
||||
.Where(lev => lev.Series != null && !string.IsNullOrEmpty(lev.Series.Name) && !string.IsNullOrEmpty(lev.Series.GetProviderId(MetadataProvider.Tvdb)))
|
||||
.OrderBy(i => i.Series.Id)
|
||||
.ToList();
|
||||
|
||||
// Can't progress further without episodes
|
||||
if (!episodes.Any())
|
||||
{
|
||||
_logger.LogInformation("episodes count is 0");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = new List<Episode>();
|
||||
var currentSeriesId = episodes[0].Series.Id;
|
||||
|
||||
foreach (var ep in episodes)
|
||||
{
|
||||
if (!currentSeriesId.Equals(ep.Series.Id))
|
||||
{
|
||||
// We're starting a new series. Time to send the current one to trakt.tv
|
||||
await _traktApi.SendLibraryUpdateAsync(payload, traktUser, eventType, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
currentSeriesId = ep.Series.Id;
|
||||
payload.Clear();
|
||||
}
|
||||
|
||||
payload.Add(ep);
|
||||
}
|
||||
|
||||
if (payload.Any())
|
||||
/// <summary>
|
||||
/// Wait for timer callback to be completed.
|
||||
/// </summary>
|
||||
private async void OnQueueTimerCallback(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _traktApi.SendLibraryUpdateAsync(payload, traktUser, eventType, CancellationToken.None).ConfigureAwait(false);
|
||||
await OnQueueTimerCallbackInternal().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception handled processing queued episode events");
|
||||
_logger.LogError(ex, "Error in OnQueueTimerCallbackInternal");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for timer to be completed.
|
||||
/// </summary>
|
||||
private async Task OnQueueTimerCallbackInternal()
|
||||
{
|
||||
_logger.LogInformation("Timer elapsed - processing queued items");
|
||||
|
||||
if (!_queuedEvents.Any())
|
||||
{
|
||||
_logger.LogInformation("No events... stopping queue timer");
|
||||
// This may need to go
|
||||
return;
|
||||
}
|
||||
|
||||
var queue = _queuedEvents.ToList();
|
||||
_queuedEvents.Clear();
|
||||
foreach (var traktUser in Plugin.Instance.PluginConfiguration.TraktUsers)
|
||||
{
|
||||
var queuedMovieDeletes = queue.Where(ev =>
|
||||
new Guid(ev.TraktUser.LinkedMbUserId).Equals(new Guid(traktUser.LinkedMbUserId)) &&
|
||||
ev.Item is Movie &&
|
||||
ev.EventType == EventType.Remove).ToList();
|
||||
|
||||
if (queuedMovieDeletes.Any())
|
||||
{
|
||||
_logger.LogInformation("{Count} movie deletions to process", queuedMovieDeletes.Count);
|
||||
await ProcessQueuedMovieEvents(queuedMovieDeletes, traktUser, EventType.Remove).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No movie deletions to process");
|
||||
}
|
||||
|
||||
var queuedMovieAdds = queue.Where(ev =>
|
||||
new Guid(ev.TraktUser.LinkedMbUserId).Equals(new Guid(traktUser.LinkedMbUserId)) &&
|
||||
ev.Item is Movie &&
|
||||
ev.EventType == EventType.Add).ToList();
|
||||
|
||||
if (queuedMovieAdds.Any())
|
||||
{
|
||||
_logger.LogInformation("{Count} movie additions to process", queuedMovieAdds.Count);
|
||||
await ProcessQueuedMovieEvents(queuedMovieAdds, traktUser, EventType.Add).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No movie additions to process");
|
||||
}
|
||||
|
||||
var queuedEpisodeDeletes = queue.Where(ev =>
|
||||
new Guid(ev.TraktUser.LinkedMbUserId).Equals(new Guid(traktUser.LinkedMbUserId)) &&
|
||||
ev.Item is Episode &&
|
||||
ev.EventType == EventType.Remove).ToList();
|
||||
|
||||
if (queuedEpisodeDeletes.Any())
|
||||
{
|
||||
_logger.LogInformation("{Count} episode deletions to process", queuedEpisodeDeletes.Count);
|
||||
await ProcessQueuedEpisodeEvents(queuedEpisodeDeletes, traktUser, EventType.Remove).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No episode deletions to process");
|
||||
}
|
||||
|
||||
var queuedEpisodeAdds = queue.Where(ev =>
|
||||
new Guid(ev.TraktUser.LinkedMbUserId).Equals(new Guid(traktUser.LinkedMbUserId)) &&
|
||||
ev.Item is Episode &&
|
||||
ev.EventType == EventType.Add).ToList();
|
||||
|
||||
if (queuedEpisodeAdds.Any())
|
||||
{
|
||||
_logger.LogInformation("{Count} episode additions to process", queuedEpisodeAdds.Count);
|
||||
await ProcessQueuedEpisodeEvents(queuedEpisodeAdds, traktUser, EventType.Add).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No episode additions to process");
|
||||
}
|
||||
|
||||
var queuedShowDeletes = queue.Where(ev =>
|
||||
new Guid(ev.TraktUser.LinkedMbUserId).Equals(new Guid(traktUser.LinkedMbUserId)) &&
|
||||
ev.Item is Series &&
|
||||
ev.EventType == EventType.Remove).ToList();
|
||||
|
||||
if (queuedShowDeletes.Any())
|
||||
{
|
||||
_logger.LogInformation("{Count} series deletions to process", queuedMovieDeletes.Count);
|
||||
await ProcessQueuedShowEvents(queuedShowDeletes, traktUser, EventType.Remove).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No series deletions to process");
|
||||
}
|
||||
}
|
||||
|
||||
// Everything is processed. Reset the event list.
|
||||
_queuedEvents.Clear();
|
||||
}
|
||||
|
||||
private async Task ProcessQueuedShowEvents(IEnumerable<LibraryEvent> events, TraktUser traktUser, EventType eventType)
|
||||
{
|
||||
var shows = events.Select(lev => (Series)lev.Item)
|
||||
.Where(lev => !string.IsNullOrEmpty(lev.Name) && !string.IsNullOrEmpty(lev.GetProviderId(MetadataProvider.Tvdb)))
|
||||
.ToList();
|
||||
try
|
||||
{
|
||||
// Should probably not be awaiting this, but it's unlikely a user will be deleting more than one or two shows at a time
|
||||
foreach (var show in shows)
|
||||
{
|
||||
await _traktApi.SendLibraryUpdateAsync(show, traktUser, eventType, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception handled processing queued series events");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes queued movie events.
|
||||
/// </summary>
|
||||
/// <param name="events">The <see cref="LibraryEvent"/> enumerable.</param>
|
||||
/// <param name="traktUser">The <see cref="TraktUser"/>.</param>
|
||||
/// <param name="eventType">The <see cref="EventType"/>.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task ProcessQueuedMovieEvents(IEnumerable<LibraryEvent> events, TraktUser traktUser, EventType eventType)
|
||||
{
|
||||
var movies = events.Select(lev => (Movie)lev.Item)
|
||||
.Where(lev => !string.IsNullOrEmpty(lev.Name) && !string.IsNullOrEmpty(lev.GetProviderId(MetadataProvider.Imdb)))
|
||||
.ToList();
|
||||
try
|
||||
{
|
||||
await _traktApi.SendLibraryUpdateAsync(movies, traktUser, eventType, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception handled processing queued movie events");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes queued episode events.
|
||||
/// </summary>
|
||||
/// <param name="events">The <see cref="LibraryEvent"/> enumerable.</param>
|
||||
/// <param name="traktUser">The <see cref="TraktUser"/>.</param>
|
||||
/// <param name="eventType">The <see cref="EventType"/>.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task ProcessQueuedEpisodeEvents(IEnumerable<LibraryEvent> events, TraktUser traktUser, EventType eventType)
|
||||
{
|
||||
var episodes = events.Select(lev => (Episode)lev.Item)
|
||||
.Where(lev => lev.Series != null && !string.IsNullOrEmpty(lev.Series.Name) && !string.IsNullOrEmpty(lev.Series.GetProviderId(MetadataProvider.Tvdb)))
|
||||
.OrderBy(i => i.Series.Id)
|
||||
.ToList();
|
||||
|
||||
// Can't progress further without episodes
|
||||
if (!episodes.Any())
|
||||
{
|
||||
_logger.LogDebug("Episodes count is 0");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = new List<Episode>();
|
||||
var currentSeriesId = episodes[0].Series.Id;
|
||||
|
||||
foreach (var ep in episodes)
|
||||
{
|
||||
if (!currentSeriesId.Equals(ep.Series.Id))
|
||||
{
|
||||
// We're starting a new series. Time to send the current one to trakt.tv
|
||||
await _traktApi.SendLibraryUpdateAsync(payload, traktUser, eventType, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
currentSeriesId = ep.Series.Id;
|
||||
payload.Clear();
|
||||
}
|
||||
|
||||
payload.Add(ep);
|
||||
}
|
||||
|
||||
if (payload.Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
await _traktApi.SendLibraryUpdateAsync(payload, traktUser, eventType, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception handled processing queued episode events");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_queueTimer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_queueTimer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,12 @@
|
||||
/// The watching state.
|
||||
/// </summary>
|
||||
Watching,
|
||||
|
||||
/// <summary>
|
||||
/// The paused state.
|
||||
/// </summary>
|
||||
Paused,
|
||||
|
||||
/// <summary>
|
||||
/// The stopped state.
|
||||
/// </summary>
|
||||
|
@ -1,28 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace Trakt.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Similar to <see cref="Progress"/>, but report is relative, not absolute.
|
||||
/// </summary>
|
||||
/// Can't be generic, because it's impossible to do arithmetics on generics
|
||||
public class SplittableProgress : Progress<double>, ISplittableProgress<double>
|
||||
namespace Trakt.Helpers
|
||||
{
|
||||
public SplittableProgress(Action<double> handler)
|
||||
: base(handler)
|
||||
/// <summary>
|
||||
/// Similar to <see cref="Progress"/>, but report is relative, not absolute.
|
||||
/// </summary>
|
||||
/// Can't be generic, because it's impossible to do arithmetics on generics.
|
||||
public class SplittableProgress : Progress<double>, ISplittableProgress<double>
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SplittableProgress"/> class.
|
||||
/// </summary>
|
||||
/// <param name="handler">Instance of the <see cref="Action"/> interface.</param>
|
||||
public SplittableProgress(Action<double> handler)
|
||||
: base(handler)
|
||||
{
|
||||
}
|
||||
|
||||
private double Progress { get; set; }
|
||||
private double Progress { get; set; }
|
||||
|
||||
public ISplittableProgress<double> Split(int parts)
|
||||
{
|
||||
var child = new SplittableProgress(
|
||||
d =>
|
||||
{
|
||||
Progress += d / parts;
|
||||
OnReport(Progress);
|
||||
});
|
||||
return child;
|
||||
/// <inheritdoc />
|
||||
public ISplittableProgress<double> Split(int parts)
|
||||
{
|
||||
var child = new SplittableProgress(
|
||||
d =>
|
||||
{
|
||||
Progress += d / parts;
|
||||
OnReport(Progress);
|
||||
});
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,207 +9,205 @@ using Microsoft.Extensions.Logging;
|
||||
using Trakt.Api;
|
||||
using Trakt.Model;
|
||||
|
||||
namespace Trakt.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class used to update the watched status of movies/episodes. Attempts to organise
|
||||
/// requests to lower trakt.tv api calls.
|
||||
/// </summary>
|
||||
internal class UserDataManagerEventsHelper : IDisposable
|
||||
namespace Trakt.Helpers
|
||||
{
|
||||
private readonly ILogger<UserDataManagerEventsHelper> _logger;
|
||||
private readonly TraktApi _traktApi;
|
||||
private readonly List<UserDataPackage> _userDataPackages;
|
||||
private Timer _timer;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Helper class used to update the watched status of movies/episodes.
|
||||
/// Attempts to organise requests to lower API calls.
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="traktApi"></param>
|
||||
public UserDataManagerEventsHelper(ILogger<UserDataManagerEventsHelper> logger, TraktApi traktApi)
|
||||
internal class UserDataManagerEventsHelper : IDisposable
|
||||
{
|
||||
_userDataPackages = new List<UserDataPackage>();
|
||||
_logger = logger;
|
||||
_traktApi = traktApi;
|
||||
}
|
||||
private readonly ILogger<UserDataManagerEventsHelper> _logger;
|
||||
private readonly TraktApi _traktApi;
|
||||
private readonly List<UserDataPackage> _userDataPackages;
|
||||
private Timer _timer;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="userDataSaveEventArgs"></param>
|
||||
/// <param name="traktUser"></param>
|
||||
public void ProcessUserDataSaveEventArgs(UserDataSaveEventArgs userDataSaveEventArgs, TraktUser traktUser)
|
||||
{
|
||||
var userPackage = _userDataPackages.FirstOrDefault(e => e.TraktUser.Equals(traktUser));
|
||||
|
||||
if (userPackage == null)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserDataManagerEventsHelper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger{UserDataManagerEventsHelper}"/>.</param>
|
||||
/// <param name="traktApi">The <see cref="TraktApi"/>.</param>
|
||||
public UserDataManagerEventsHelper(ILogger<UserDataManagerEventsHelper> logger, TraktApi traktApi)
|
||||
{
|
||||
userPackage = new UserDataPackage { TraktUser = traktUser };
|
||||
_userDataPackages.Add(userPackage);
|
||||
_userDataPackages = new List<UserDataPackage>();
|
||||
_logger = logger;
|
||||
_traktApi = traktApi;
|
||||
}
|
||||
|
||||
if (_timer == null)
|
||||
/// <summary>
|
||||
/// Process user data save event for trakt.tv users.
|
||||
/// </summary>
|
||||
/// <param name="userDataSaveEventArgs">The <see cref="UserDataSaveEventArgs"/>.</param>
|
||||
/// <param name="traktUser">The <see cref="TraktUser"/>.</param>
|
||||
public void ProcessUserDataSaveEventArgs(UserDataSaveEventArgs userDataSaveEventArgs, TraktUser traktUser)
|
||||
{
|
||||
_timer = new Timer(
|
||||
OnTimerCallback,
|
||||
null,
|
||||
TimeSpan.FromMilliseconds(5000),
|
||||
Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_timer.Change(TimeSpan.FromMilliseconds(5000), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
var userPackage = _userDataPackages.FirstOrDefault(user => user.TraktUser.Equals(traktUser));
|
||||
|
||||
if (userPackage == null)
|
||||
{
|
||||
_userDataPackages.Add(new UserDataPackage { TraktUser = traktUser });
|
||||
}
|
||||
|
||||
if (_timer == null)
|
||||
{
|
||||
_timer = new Timer(
|
||||
OnTimerCallback,
|
||||
null,
|
||||
TimeSpan.FromMilliseconds(3000),
|
||||
Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_timer.Change(TimeSpan.FromMilliseconds(5000), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
if (userDataSaveEventArgs.Item is Movie movie)
|
||||
{
|
||||
if (userDataSaveEventArgs.UserData.Played)
|
||||
{
|
||||
if (traktUser.PostSetWatched)
|
||||
{
|
||||
userPackage.SeenMovies.Add(movie);
|
||||
}
|
||||
|
||||
// Force update trakt.tv if we have more than 100 seen movies in the queue due to API
|
||||
if (userPackage.SeenMovies.Count >= 100)
|
||||
{
|
||||
_traktApi.SendMoviePlaystateUpdates(
|
||||
userPackage.SeenMovies,
|
||||
userPackage.TraktUser,
|
||||
true,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
userPackage.SeenMovies.Clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (traktUser.PostSetUnwatched)
|
||||
{
|
||||
userPackage.UnSeenMovies.Add(movie);
|
||||
}
|
||||
|
||||
// Force update trakt.tv if we have more than 100 unseen movies in the queue due to API
|
||||
if (userPackage.UnSeenMovies.Count >= 100)
|
||||
{
|
||||
_traktApi.SendMoviePlaystateUpdates(
|
||||
userPackage.UnSeenMovies,
|
||||
userPackage.TraktUser,
|
||||
false,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
userPackage.UnSeenMovies.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(userDataSaveEventArgs.Item is Episode episode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's not the series we're currently storing, upload our episodes and reset the arrays
|
||||
if (!userPackage.CurrentSeriesId.Equals(episode.Series.Id))
|
||||
{
|
||||
if (userPackage.SeenEpisodes.Any())
|
||||
{
|
||||
_traktApi.SendEpisodePlaystateUpdates(
|
||||
userPackage.SeenEpisodes,
|
||||
userPackage.TraktUser,
|
||||
true,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
userPackage.SeenEpisodes.Clear();
|
||||
}
|
||||
|
||||
if (userPackage.UnSeenEpisodes.Any())
|
||||
{
|
||||
_traktApi.SendEpisodePlaystateUpdates(
|
||||
userPackage.UnSeenEpisodes,
|
||||
userPackage.TraktUser,
|
||||
false,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
userPackage.UnSeenEpisodes.Clear();
|
||||
}
|
||||
|
||||
userPackage.CurrentSeriesId = episode.Series.Id;
|
||||
}
|
||||
|
||||
if (userDataSaveEventArgs.Item is Movie movie)
|
||||
{
|
||||
if (userDataSaveEventArgs.UserData.Played)
|
||||
{
|
||||
if (traktUser.PostSetWatched)
|
||||
{
|
||||
userPackage.SeenMovies.Add(movie);
|
||||
}
|
||||
|
||||
if (userPackage.SeenMovies.Count >= 100)
|
||||
{
|
||||
_traktApi.SendMoviePlaystateUpdates(
|
||||
userPackage.SeenMovies,
|
||||
userPackage.TraktUser,
|
||||
true,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
userPackage.SeenMovies = new List<Movie>();
|
||||
userPackage.SeenEpisodes.Add(episode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (traktUser.PostSetUnwatched)
|
||||
{
|
||||
userPackage.UnSeenMovies.Add(movie);
|
||||
userPackage.UnSeenEpisodes.Add(episode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (userPackage.UnSeenMovies.Count >= 100)
|
||||
private void OnTimerCallback(object state)
|
||||
{
|
||||
foreach (var package in _userDataPackages)
|
||||
{
|
||||
if (package.UnSeenMovies.Any())
|
||||
{
|
||||
_traktApi.SendMoviePlaystateUpdates(
|
||||
userPackage.UnSeenMovies,
|
||||
userPackage.TraktUser,
|
||||
package.UnSeenMovies.ToList(),
|
||||
package.TraktUser,
|
||||
false,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
userPackage.UnSeenMovies = new List<Movie>();
|
||||
package.UnSeenMovies.Clear();
|
||||
}
|
||||
|
||||
if (package.SeenMovies.Any())
|
||||
{
|
||||
_traktApi.SendMoviePlaystateUpdates(
|
||||
package.SeenMovies.ToList(),
|
||||
package.TraktUser,
|
||||
true,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
package.SeenMovies.Clear();
|
||||
}
|
||||
|
||||
if (package.UnSeenEpisodes.Any())
|
||||
{
|
||||
_traktApi.SendEpisodePlaystateUpdates(
|
||||
package.UnSeenEpisodes.ToList(),
|
||||
package.TraktUser,
|
||||
false,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
package.UnSeenEpisodes.Clear();
|
||||
}
|
||||
|
||||
if (package.SeenEpisodes.Any())
|
||||
{
|
||||
_traktApi.SendEpisodePlaystateUpdates(
|
||||
package.SeenEpisodes.ToList(),
|
||||
package.TraktUser,
|
||||
true,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
package.SeenEpisodes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(userDataSaveEventArgs.Item is Episode episode))
|
||||
public void Dispose()
|
||||
{
|
||||
return;
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// If it's not the series we're currently storing, upload our episodes and reset the arrays
|
||||
if (!userPackage.CurrentSeriesId.Equals(episode.Series.Id))
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (userPackage.SeenEpisodes.Any())
|
||||
if (disposing)
|
||||
{
|
||||
_traktApi.SendEpisodePlaystateUpdates(
|
||||
userPackage.SeenEpisodes,
|
||||
userPackage.TraktUser,
|
||||
true,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
userPackage.SeenEpisodes = new List<Episode>();
|
||||
_timer.Dispose();
|
||||
}
|
||||
|
||||
if (userPackage.UnSeenEpisodes.Any())
|
||||
{
|
||||
_traktApi.SendEpisodePlaystateUpdates(
|
||||
userPackage.UnSeenEpisodes,
|
||||
userPackage.TraktUser,
|
||||
false,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
userPackage.UnSeenEpisodes = new List<Episode>();
|
||||
}
|
||||
|
||||
userPackage.CurrentSeriesId = episode.Series.Id;
|
||||
}
|
||||
|
||||
if (userDataSaveEventArgs.UserData.Played)
|
||||
{
|
||||
if (traktUser.PostSetWatched)
|
||||
{
|
||||
userPackage.SeenEpisodes.Add(episode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (traktUser.PostSetUnwatched)
|
||||
{
|
||||
userPackage.UnSeenEpisodes.Add(episode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTimerCallback(object state)
|
||||
{
|
||||
foreach (var package in _userDataPackages)
|
||||
{
|
||||
if (package.UnSeenMovies.Any())
|
||||
{
|
||||
var movies = package.UnSeenMovies.ToList();
|
||||
package.UnSeenMovies.Clear();
|
||||
_traktApi.SendMoviePlaystateUpdates(
|
||||
movies,
|
||||
package.TraktUser,
|
||||
false,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (package.SeenMovies.Any())
|
||||
{
|
||||
var movies = package.SeenMovies.ToList();
|
||||
package.SeenMovies.Clear();
|
||||
_traktApi.SendMoviePlaystateUpdates(
|
||||
movies,
|
||||
package.TraktUser,
|
||||
true,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (package.UnSeenEpisodes.Any())
|
||||
{
|
||||
var episodes = package.UnSeenEpisodes.ToList();
|
||||
package.UnSeenEpisodes.Clear();
|
||||
_traktApi.SendEpisodePlaystateUpdates(
|
||||
episodes,
|
||||
package.TraktUser,
|
||||
false,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (package.SeenEpisodes.Any())
|
||||
{
|
||||
var episodes = package.SeenEpisodes.ToList();
|
||||
package.SeenEpisodes.Clear();
|
||||
_traktApi.SendEpisodePlaystateUpdates(
|
||||
episodes,
|
||||
package.TraktUser,
|
||||
true,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,30 +4,31 @@ using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using Trakt.Model;
|
||||
|
||||
namespace Trakt.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Class that contains all the items to be reported to trakt.tv and supporting properties.
|
||||
/// </summary>
|
||||
internal class UserDataPackage
|
||||
namespace Trakt.Helpers
|
||||
{
|
||||
public UserDataPackage()
|
||||
/// <summary>
|
||||
/// Class that contains all the items to be reported to trakt.tv and supporting properties.
|
||||
/// </summary>
|
||||
internal class UserDataPackage
|
||||
{
|
||||
SeenMovies = new List<Movie>();
|
||||
UnSeenMovies = new List<Movie>();
|
||||
SeenEpisodes = new List<Episode>();
|
||||
UnSeenEpisodes = new List<Episode>();
|
||||
public UserDataPackage()
|
||||
{
|
||||
SeenMovies = new List<Movie>();
|
||||
UnSeenMovies = new List<Movie>();
|
||||
SeenEpisodes = new List<Episode>();
|
||||
UnSeenEpisodes = new List<Episode>();
|
||||
}
|
||||
|
||||
public TraktUser TraktUser { get; set; }
|
||||
|
||||
public Guid CurrentSeriesId { get; set; }
|
||||
|
||||
public List<Movie> SeenMovies { get; set; }
|
||||
|
||||
public List<Movie> UnSeenMovies { get; set; }
|
||||
|
||||
public List<Episode> SeenEpisodes { get; set; }
|
||||
|
||||
public List<Episode> UnSeenEpisodes { get; set; }
|
||||
}
|
||||
|
||||
public TraktUser TraktUser { get; set; }
|
||||
|
||||
public Guid CurrentSeriesId { get; set; }
|
||||
|
||||
public List<Movie> SeenMovies { get; set; }
|
||||
|
||||
public List<Movie> UnSeenMovies { get; set; }
|
||||
|
||||
public List<Episode> SeenEpisodes { get; set; }
|
||||
|
||||
public List<Episode> UnSeenEpisodes { get; set; }
|
||||
}
|
||||
|
@ -1,44 +1,44 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using Trakt.Model;
|
||||
|
||||
namespace Trakt.Helpers;
|
||||
|
||||
internal static class UserHelper
|
||||
namespace Trakt.Helpers
|
||||
{
|
||||
public static TraktUser GetTraktUser(User user)
|
||||
internal static class UserHelper
|
||||
{
|
||||
return GetTraktUser(user.Id);
|
||||
}
|
||||
|
||||
public static TraktUser GetTraktUser(string userId)
|
||||
{
|
||||
return GetTraktUser(new Guid(userId));
|
||||
}
|
||||
|
||||
public static TraktUser GetTraktUser(Guid userGuid)
|
||||
{
|
||||
if (Plugin.Instance.PluginConfiguration.TraktUsers == null)
|
||||
public static TraktUser GetTraktUser(User user)
|
||||
{
|
||||
return null;
|
||||
return GetTraktUser(user.Id);
|
||||
}
|
||||
|
||||
return Plugin.Instance.PluginConfiguration.TraktUsers.FirstOrDefault(tUser =>
|
||||
public static TraktUser GetTraktUser(string userId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tUser.LinkedMbUserId))
|
||||
return GetTraktUser(new Guid(userId));
|
||||
}
|
||||
|
||||
public static TraktUser GetTraktUser(Guid userGuid)
|
||||
{
|
||||
if (Plugin.Instance.PluginConfiguration.TraktUsers == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Plugin.Instance.PluginConfiguration.TraktUsers.FirstOrDefault(user =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(user.LinkedMbUserId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Guid.TryParse(user.LinkedMbUserId, out Guid traktUserGuid)
|
||||
&& traktUserGuid.Equals(userGuid))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Guid.TryParse(tUser.LinkedMbUserId, out Guid traktUserGuid)
|
||||
&& traktUserGuid.Equals(userGuid))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
82
Trakt/Model/TraktAudio.cs
Normal file
82
Trakt/Model/TraktAudio.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Trakt.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum TraktAudio.
|
||||
/// </summary>
|
||||
public enum TraktAudio
|
||||
{
|
||||
/// <summary>
|
||||
/// LPCM audio.
|
||||
/// </summary>
|
||||
[Description("lpcm")]
|
||||
Lpcm,
|
||||
|
||||
/// <summary>
|
||||
/// MP3 audio.
|
||||
/// </summary>
|
||||
[Description("mp3")]
|
||||
Mp3,
|
||||
|
||||
/// <summary>
|
||||
/// AAC audio.
|
||||
/// </summary>
|
||||
[Description("aac")]
|
||||
Aac,
|
||||
|
||||
/// <summary>
|
||||
/// DTS audio.
|
||||
/// </summary>
|
||||
[Description("dts")]
|
||||
Dts,
|
||||
|
||||
/// <summary>
|
||||
/// DTS-HD Master Audio audio.
|
||||
/// </summary>
|
||||
[Description("dts_ma")]
|
||||
DtsMa,
|
||||
|
||||
/// <summary>
|
||||
/// FLAC audio.
|
||||
/// </summary>
|
||||
[Description("flac")]
|
||||
Flac,
|
||||
|
||||
/// <summary>
|
||||
/// OGG audio.
|
||||
/// </summary>
|
||||
[Description("ogg")]
|
||||
Ogg,
|
||||
|
||||
/// <summary>
|
||||
/// WMA audio.
|
||||
/// </summary>
|
||||
[Description("wma")]
|
||||
Wma,
|
||||
|
||||
/// <summary>
|
||||
/// Dolby ProLogic audio.
|
||||
/// </summary>
|
||||
[Description("dolby_prologic")]
|
||||
DolbyProLogic,
|
||||
|
||||
/// <summary>
|
||||
/// Dolby Digital audio.
|
||||
/// </summary>
|
||||
[Description("dolby_digital")]
|
||||
DolbyDigital,
|
||||
|
||||
/// <summary>
|
||||
/// Dolby Digital Plus audio.
|
||||
/// </summary>
|
||||
[Description("dolby_digital_plus")]
|
||||
DolbyDigitalPlus,
|
||||
|
||||
/// <summary>
|
||||
/// Dolby TrueHD audio.
|
||||
/// </summary>
|
||||
[Description("dolby_truehd")]
|
||||
DolbyTrueHd
|
||||
}
|
||||
}
|
@ -2,53 +2,108 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace Trakt.Model;
|
||||
|
||||
public class TraktUser
|
||||
namespace Trakt.Model
|
||||
{
|
||||
public TraktUser()
|
||||
/// <summary>
|
||||
/// Trakt.tv user class.
|
||||
/// </summary>
|
||||
public class TraktUser
|
||||
{
|
||||
SkipUnwatchedImportFromTrakt = true;
|
||||
SkipWatchedImportFromTrakt = false;
|
||||
PostWatchedHistory = true;
|
||||
PostUnwatchedHistory = true;
|
||||
PostSetWatched = true;
|
||||
PostSetUnwatched = true;
|
||||
ExtraLogging = false;
|
||||
ExportMediaInfo = false;
|
||||
SynchronizeCollections = true;
|
||||
Scrobble = true;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TraktUser"/> class.
|
||||
/// </summary>
|
||||
public TraktUser()
|
||||
{
|
||||
SkipUnwatchedImportFromTrakt = true;
|
||||
SkipWatchedImportFromTrakt = false;
|
||||
PostWatchedHistory = true;
|
||||
PostUnwatchedHistory = true;
|
||||
PostSetWatched = true;
|
||||
PostSetUnwatched = true;
|
||||
ExtraLogging = false;
|
||||
ExportMediaInfo = false;
|
||||
SynchronizeCollections = true;
|
||||
Scrobble = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the access token.
|
||||
/// </summary>
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the refresh token.
|
||||
/// </summary>
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the linked Mb user id.
|
||||
/// </summary>
|
||||
public string LinkedMbUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the advanced rating option is enabled or not.
|
||||
/// </summary>
|
||||
public bool UsesAdvancedRating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the skip unwatched import option is enabled or not.
|
||||
/// </summary>
|
||||
public bool SkipUnwatchedImportFromTrakt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the skip watched import option is enabled or not.
|
||||
/// </summary>
|
||||
public bool SkipWatchedImportFromTrakt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the watch history should be posted or not.
|
||||
/// </summary>
|
||||
public bool PostWatchedHistory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the unwatch history should be posted or not.
|
||||
/// </summary>
|
||||
public bool PostUnwatchedHistory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether setting an item to watched should be posted or not.
|
||||
/// </summary>
|
||||
public bool PostSetWatched { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the setting an item to unwatched should be posted or not.
|
||||
/// </summary>
|
||||
public bool PostSetUnwatched { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether extra logging is enabled or not.
|
||||
/// </summary>
|
||||
public bool ExtraLogging { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the media info should be exported or not.
|
||||
/// </summary>
|
||||
public bool ExportMediaInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether collections should be synchronized or not.
|
||||
/// </summary>
|
||||
public bool SynchronizeCollections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether scrobbling should take place or not.
|
||||
/// </summary>
|
||||
public bool Scrobble { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the access token.
|
||||
/// </summary>
|
||||
public string[] LocationsExcluded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the access token expiration.
|
||||
/// </summary>
|
||||
public DateTime AccessTokenExpiration { get; set; }
|
||||
}
|
||||
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
public string LinkedMbUserId { get; set; }
|
||||
|
||||
public bool UsesAdvancedRating { get; set; }
|
||||
|
||||
public bool SkipUnwatchedImportFromTrakt { get; set; }
|
||||
|
||||
public bool SkipWatchedImportFromTrakt { get; set; }
|
||||
|
||||
public bool PostWatchedHistory { get; set; }
|
||||
|
||||
public bool PostUnwatchedHistory { get; set; }
|
||||
|
||||
public bool PostSetWatched { get; set; }
|
||||
|
||||
public bool PostSetUnwatched { get; set; }
|
||||
|
||||
public bool ExtraLogging { get; set; }
|
||||
|
||||
public bool ExportMediaInfo { get; set; }
|
||||
|
||||
public bool SynchronizeCollections { get; set; }
|
||||
|
||||
public bool Scrobble { get; set; }
|
||||
|
||||
public string[] LocationsExcluded { get; set; }
|
||||
|
||||
public DateTime AccessTokenExpiration { get; set; }
|
||||
}
|
||||
|
@ -7,48 +7,68 @@ using MediaBrowser.Model.Plugins;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Trakt.Configuration;
|
||||
|
||||
namespace Trakt;
|
||||
|
||||
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
namespace Trakt
|
||||
{
|
||||
public Plugin(IApplicationPaths appPaths, IXmlSerializer xmlSerializer)
|
||||
: base(appPaths, xmlSerializer)
|
||||
/// <summary>
|
||||
/// Plugin class for the track.tv syncing.
|
||||
/// </summary>
|
||||
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
{
|
||||
Instance = this;
|
||||
PollingTasks = new Dictionary<string, Task<bool>>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Trakt";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Guid Id => new Guid("4fe3201e-d6ae-4f2e-8917-e12bda571281");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Description
|
||||
=> "Watch, rate, and discover media using Trakt. The HTPC just got more social.";
|
||||
|
||||
public static Plugin Instance { get; private set; }
|
||||
|
||||
public PluginConfiguration PluginConfiguration => Configuration;
|
||||
|
||||
public Dictionary<string, Task<bool>> PollingTasks { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginPageInfo> GetPages()
|
||||
{
|
||||
return new[]
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Plugin"/> class.
|
||||
/// </summary>
|
||||
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
|
||||
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
|
||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
new PluginPageInfo
|
||||
Instance = this;
|
||||
PollingTasks = new Dictionary<string, Task<bool>>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Trakt";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Guid Id => new Guid("4fe3201e-d6ae-4f2e-8917-e12bda571281");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Description => "Sync your library to trakt.tv and scrobble your watch status.";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of trakt.tv plugin.
|
||||
/// </summary>
|
||||
public static Plugin Instance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin configuration.
|
||||
/// </summary>
|
||||
public PluginConfiguration PluginConfiguration => Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the polling tasks.
|
||||
/// </summary>
|
||||
public Dictionary<string, Task<bool>> PollingTasks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Return the plugin configuration page.
|
||||
/// </summary>
|
||||
/// <returns>PluginPageInfo.</returns>
|
||||
public IEnumerable<PluginPageInfo> GetPages()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
Name = "trakt",
|
||||
EmbeddedResourcePath = GetType().Namespace + ".Web.trakt.html",
|
||||
},
|
||||
new PluginPageInfo
|
||||
{
|
||||
Name = "traktjs",
|
||||
EmbeddedResourcePath = GetType().Namespace + ".Web.trakt.js"
|
||||
}
|
||||
};
|
||||
new PluginPageInfo
|
||||
{
|
||||
Name = "trakt",
|
||||
EmbeddedResourcePath = GetType().Namespace + ".Web.trakt.html",
|
||||
},
|
||||
new PluginPageInfo
|
||||
{
|
||||
Name = "traktjs",
|
||||
EmbeddedResourcePath = GetType().Namespace + ".Web.trakt.js"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,277 +24,175 @@ using Trakt.Api.DataContracts.Users.Watched;
|
||||
using Trakt.Helpers;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
|
||||
namespace Trakt.ScheduledTasks;
|
||||
|
||||
/// <summary>
|
||||
/// Task that will Sync each users trakt.tv profile with their local library. This task will only include
|
||||
/// watched states.
|
||||
/// </summary>
|
||||
public class SyncFromTraktTask : IScheduledTask
|
||||
namespace Trakt.ScheduledTasks
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger<SyncFromTraktTask> _logger;
|
||||
private readonly TraktApi _traktApi;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Task that will Sync each users trakt.tv profile with their local library. This task will only include
|
||||
/// watched states.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory"></param>
|
||||
/// <param name="userManager"></param>
|
||||
/// <param name="userDataManager"> </param>
|
||||
/// <param name="httpClient"></param>
|
||||
/// <param name="appHost"></param>
|
||||
/// <param name="fileSystem"></param>
|
||||
public SyncFromTraktTask(
|
||||
ILoggerFactory loggerFactory,
|
||||
IUserManager userManager,
|
||||
IUserDataManager userDataManager,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IServerApplicationHost appHost,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager)
|
||||
public class SyncFromTraktTask : IScheduledTask
|
||||
{
|
||||
_userManager = userManager;
|
||||
_userDataManager = userDataManager;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = loggerFactory.CreateLogger<SyncFromTraktTask>();
|
||||
_traktApi = new TraktApi(loggerFactory.CreateLogger<TraktApi>(), httpClientFactory, appHost, userDataManager, fileSystem);
|
||||
}
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger<SyncFromTraktTask> _logger;
|
||||
private readonly TraktApi _traktApi;
|
||||
|
||||
public string Key => "TraktSyncFromTraktTask";
|
||||
|
||||
public string Name => "Import playstates from Trakt.tv";
|
||||
|
||||
public string Description => "Sync Watched/Unwatched status from Trakt.tv for each Jellyfin user that has a configured Trakt account";
|
||||
|
||||
public string Category => "Trakt";
|
||||
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() => Enumerable.Empty<TaskTriggerInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Gather users and call <see cref="SyncTraktDataForUser"/>
|
||||
/// </summary>
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var users = _userManager.Users.Where(u => UserHelper.GetTraktUser(u) != null).ToList();
|
||||
|
||||
// No point going further if we don't have users.
|
||||
if (users.Count == 0)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SyncFromTraktTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
|
||||
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||
/// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
|
||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||
public SyncFromTraktTask(
|
||||
ILoggerFactory loggerFactory,
|
||||
IUserManager userManager,
|
||||
IUserDataManager userDataManager,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IServerApplicationHost appHost,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger.LogInformation("No Users returned");
|
||||
return;
|
||||
_userManager = userManager;
|
||||
_userDataManager = userDataManager;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = loggerFactory.CreateLogger<SyncFromTraktTask>();
|
||||
_traktApi = new TraktApi(loggerFactory.CreateLogger<TraktApi>(), httpClientFactory, appHost, userDataManager, fileSystem);
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
var percentPerUser = 100 / users.Count;
|
||||
double currentProgress = 0;
|
||||
var numComplete = 0;
|
||||
/// <inheritdoc />
|
||||
public string Key => "TraktSyncFromTraktTask";
|
||||
|
||||
foreach (var user in users)
|
||||
/// <inheritdoc />
|
||||
public string Name => "Import playstates from Trakt.tv";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => "Sync Watched/Unwatched status from Trakt.tv for each Jellyfin user that has a configured trakt.tv account";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Category => "Trakt";
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() => Enumerable.Empty<TaskTriggerInfo>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var users = _userManager.Users.Where(u => UserHelper.GetTraktUser(u) != null).ToList();
|
||||
|
||||
// No point going further if we don't have users.
|
||||
if (users.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("No Users returned");
|
||||
return;
|
||||
}
|
||||
|
||||
// Purely for progress reporting
|
||||
var percentPerUser = 100 / users.Count;
|
||||
double currentProgress = 0;
|
||||
var numComplete = 0;
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SyncTraktDataForUser(user, currentProgress, progress, percentPerUser, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
numComplete++;
|
||||
currentProgress = percentPerUser * numComplete;
|
||||
progress.Report(currentProgress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error syncing trakt.tv data for user {UserName}", user.Username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SyncTraktDataForUser(Jellyfin.Data.Entities.User user, double currentProgress, IProgress<double> progress, double percentPerUser, CancellationToken cancellationToken)
|
||||
{
|
||||
var traktUser = UserHelper.GetTraktUser(user);
|
||||
|
||||
List<TraktMovieWatched> traktWatchedMovies;
|
||||
List<TraktShowWatched> traktWatchedShows;
|
||||
|
||||
try
|
||||
{
|
||||
await SyncTraktDataForUser(user, currentProgress, progress, percentPerUser, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
numComplete++;
|
||||
currentProgress = percentPerUser * numComplete;
|
||||
progress.Report(currentProgress);
|
||||
/*
|
||||
* In order to be as accurate as possible. We need to download the users show collection & the users watched shows.
|
||||
* It's unfortunate that trakt.tv doesn't explicitly supply a bulk method to determine shows that have not been watched
|
||||
* like they do for movies.
|
||||
*/
|
||||
traktWatchedMovies = await _traktApi.SendGetAllWatchedMoviesRequest(traktUser).ConfigureAwait(false);
|
||||
traktWatchedShows = await _traktApi.SendGetWatchedShowsRequest(traktUser).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error syncing trakt data for user {UserName}", user.Username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SyncTraktDataForUser(Jellyfin.Data.Entities.User user, double currentProgress, IProgress<double> progress, double percentPerUser, CancellationToken cancellationToken)
|
||||
{
|
||||
var traktUser = UserHelper.GetTraktUser(user);
|
||||
|
||||
List<TraktMovieWatched> traktWatchedMovies;
|
||||
List<TraktShowWatched> traktWatchedShows;
|
||||
|
||||
try
|
||||
{
|
||||
/*
|
||||
* In order to be as accurate as possible. We need to download the users show collection & the users watched shows.
|
||||
* It's unfortunate that trakt.tv doesn't explicitly supply a bulk method to determine shows that have not been watched
|
||||
* like they do for movies.
|
||||
*/
|
||||
traktWatchedMovies = await _traktApi.SendGetAllWatchedMoviesRequest(traktUser).ConfigureAwait(false);
|
||||
traktWatchedShows = await _traktApi.SendGetWatchedShowsRequest(traktUser).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception handled");
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Trakt.tv watched Movies count = {Count}", traktWatchedMovies.Count);
|
||||
_logger.LogInformation("Trakt.tv watched Shows count = {Count}", traktWatchedShows.Count);
|
||||
|
||||
var mediaItems =
|
||||
_libraryManager.GetItemList(
|
||||
new InternalItemsQuery(user) { IncludeItemTypes = new[] { BaseItemKind.Movie, BaseItemKind.Episode }, IsVirtualItem = false, OrderBy = new[] { (ItemSortBy.SeriesSortName, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending) } })
|
||||
.Where(i => _traktApi.CanSync(i, traktUser)).ToList();
|
||||
|
||||
// purely for progress reporting
|
||||
var percentPerItem = percentPerUser / mediaItems.Count;
|
||||
|
||||
foreach (var movie in mediaItems.OfType<Movie>())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var matchedMovie = FindMatch(movie, traktWatchedMovies);
|
||||
|
||||
if (matchedMovie != null)
|
||||
{
|
||||
_logger.LogDebug("Movie is in Watched list {Name}", movie.Name);
|
||||
|
||||
var userData = _userDataManager.GetUserData(user.Id, movie);
|
||||
bool changed = false;
|
||||
|
||||
DateTime? tLastPlayed = null;
|
||||
if (DateTime.TryParse(matchedMovie.LastWatchedAt, out var value))
|
||||
{
|
||||
tLastPlayed = value;
|
||||
}
|
||||
|
||||
// set movie as watched
|
||||
if (!userData.Played)
|
||||
{
|
||||
userData.Played = true;
|
||||
userData.LastPlayedDate = tLastPlayed ?? DateTime.Now;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// keep the highest play count
|
||||
if (userData.PlayCount < matchedMovie.Plays)
|
||||
{
|
||||
userData.PlayCount = matchedMovie.Plays;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Update last played if remote time is more recent
|
||||
if (tLastPlayed != null && userData.LastPlayedDate < tLastPlayed)
|
||||
{
|
||||
userData.LastPlayedDate = tLastPlayed;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Only process if there's a change
|
||||
if (changed)
|
||||
{
|
||||
_userDataManager.SaveUserData(
|
||||
user.Id,
|
||||
movie,
|
||||
userData,
|
||||
UserDataSaveReason.Import,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// _logger.LogInformation("Failed to match " + movie.Name);
|
||||
_logger.LogError(ex, "Exception handled");
|
||||
throw;
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
currentProgress += percentPerItem;
|
||||
progress.Report(currentProgress);
|
||||
}
|
||||
_logger.LogInformation("Trakt.tv watched Movies count = {Count}", traktWatchedMovies.Count);
|
||||
_logger.LogInformation("Trakt.tv watched Shows count = {Count}", traktWatchedShows.Count);
|
||||
|
||||
foreach (var episode in mediaItems.OfType<Episode>())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var matchedShow = FindMatch(episode.Series, traktWatchedShows);
|
||||
var mediaItems =
|
||||
_libraryManager.GetItemList(
|
||||
new InternalItemsQuery(user) { IncludeItemTypes = new[] { BaseItemKind.Movie, BaseItemKind.Episode }, IsVirtualItem = false, OrderBy = new[] { (ItemSortBy.SeriesSortName, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending) } })
|
||||
.Where(i => _traktApi.CanSync(i, traktUser)).ToList();
|
||||
|
||||
if (matchedShow != null)
|
||||
// Purely for progress reporting
|
||||
var percentPerItem = percentPerUser / mediaItems.Count;
|
||||
|
||||
foreach (var movie in mediaItems.OfType<Movie>())
|
||||
{
|
||||
var matchedSeason =
|
||||
matchedShow.Seasons.FirstOrDefault(
|
||||
tSeason =>
|
||||
tSeason.Number
|
||||
== (episode.ParentIndexNumber == 0
|
||||
? 0
|
||||
: episode.ParentIndexNumber ?? 1));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var matchedMovie = FindMatch(movie, traktWatchedMovies);
|
||||
|
||||
// keep track of the shows rewatch cycles
|
||||
DateTime? tLastReset = null;
|
||||
if (DateTime.TryParse(matchedShow.ResetAt, out var resetValue))
|
||||
if (matchedMovie != null)
|
||||
{
|
||||
tLastReset = resetValue;
|
||||
}
|
||||
_logger.LogDebug("Movie is in Watched list {Name}", movie.Name);
|
||||
|
||||
// if it's not a match then it means trakt doesn't know about the season, leave the watched state alone and move on
|
||||
if (matchedSeason != null)
|
||||
{
|
||||
// episode is in users libary. Now we need to determine if it's watched
|
||||
var userData = _userDataManager.GetUserData(user.Id, episode);
|
||||
var userData = _userDataManager.GetUserData(user.Id, movie);
|
||||
bool changed = false;
|
||||
|
||||
var matchedEpisode =
|
||||
matchedSeason.Episodes.FirstOrDefault(x => x.Number == (episode.IndexNumber ?? -1));
|
||||
|
||||
// prepend a check if the matched episode is on a rewatch cycle and
|
||||
// discard it if the last play date was before the reset date
|
||||
if (matchedEpisode != null && tLastReset != null)
|
||||
DateTime? tLastPlayed = null;
|
||||
if (DateTime.TryParse(matchedMovie.LastWatchedAt, out var value))
|
||||
{
|
||||
if (DateTime.TryParse(matchedEpisode.LastWatchedAt, out var value) && value < tLastReset)
|
||||
{
|
||||
matchedEpisode = null;
|
||||
}
|
||||
tLastPlayed = value;
|
||||
}
|
||||
|
||||
if (matchedEpisode != null)
|
||||
// Set movie as watched
|
||||
if (!userData.Played)
|
||||
{
|
||||
_logger.LogDebug("Episode is in Watched list {Data}", GetVerboseEpisodeData(episode));
|
||||
|
||||
if (!traktUser.SkipWatchedImportFromTrakt)
|
||||
{
|
||||
DateTime? tLastPlayed = null;
|
||||
if (DateTime.TryParse(matchedEpisode.LastWatchedAt, out var value))
|
||||
{
|
||||
tLastPlayed = value;
|
||||
}
|
||||
|
||||
// Set episode as watched
|
||||
if (!userData.Played)
|
||||
{
|
||||
userData.Played = true;
|
||||
userData.LastPlayedDate = tLastPlayed ?? DateTime.Now;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// keep the highest play count
|
||||
if (userData.PlayCount < matchedEpisode.Plays)
|
||||
{
|
||||
userData.PlayCount = matchedEpisode.Plays;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Update last played if remote time is more recent
|
||||
if (tLastPlayed != null && userData.LastPlayedDate < tLastPlayed)
|
||||
{
|
||||
userData.LastPlayedDate = tLastPlayed;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!traktUser.SkipUnwatchedImportFromTrakt)
|
||||
{
|
||||
userData.Played = false;
|
||||
userData.PlayCount = 0;
|
||||
userData.LastPlayedDate = null;
|
||||
userData.Played = true;
|
||||
userData.LastPlayedDate = tLastPlayed ?? DateTime.Now;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// only process if changed
|
||||
// Keep the highest play count
|
||||
if (userData.PlayCount < matchedMovie.Plays)
|
||||
{
|
||||
userData.PlayCount = matchedMovie.Plays;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Update last played if remote time is more recent
|
||||
if (tLastPlayed != null && userData.LastPlayedDate < tLastPlayed)
|
||||
{
|
||||
userData.LastPlayedDate = tLastPlayed;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Only process if there's a change
|
||||
if (changed)
|
||||
{
|
||||
_userDataManager.SaveUserData(
|
||||
user.Id,
|
||||
episode,
|
||||
movie,
|
||||
userData,
|
||||
UserDataSaveReason.Import,
|
||||
cancellationToken);
|
||||
@ -302,102 +200,243 @@ public class SyncFromTraktTask : IScheduledTask
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("No Season match in Watched shows list {Episode}", GetVerboseEpisodeData(episode));
|
||||
_logger.LogDebug("Failed to match {Movie}", movie.Name);
|
||||
}
|
||||
|
||||
// Purely for progress reporting
|
||||
currentProgress += percentPerItem;
|
||||
progress.Report(currentProgress);
|
||||
}
|
||||
else
|
||||
|
||||
foreach (var episode in mediaItems.OfType<Episode>())
|
||||
{
|
||||
_logger.LogDebug("No Show match in Watched shows list {Episode}", GetVerboseEpisodeData(episode));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var matchedShow = FindMatch(episode.Series, traktWatchedShows);
|
||||
|
||||
if (matchedShow != null)
|
||||
{
|
||||
var matchedSeason =
|
||||
matchedShow.Seasons.FirstOrDefault(
|
||||
tSeason =>
|
||||
tSeason.Number
|
||||
== (episode.ParentIndexNumber == 0
|
||||
? 0
|
||||
: episode.ParentIndexNumber ?? 1));
|
||||
|
||||
// Keep track of the shows rewatch cycles
|
||||
DateTime? tLastReset = null;
|
||||
if (DateTime.TryParse(matchedShow.ResetAt, out var resetValue))
|
||||
{
|
||||
tLastReset = resetValue;
|
||||
}
|
||||
|
||||
// If it's not a match then it means trakt.tv doesn't know about the season, leave the watched state alone and move on
|
||||
if (matchedSeason != null)
|
||||
{
|
||||
// Episode is in users libary. Now we need to determine if it's watched
|
||||
var userData = _userDataManager.GetUserData(user.Id, episode);
|
||||
bool changed = false;
|
||||
|
||||
var matchedEpisode =
|
||||
matchedSeason.Episodes.FirstOrDefault(x => x.Number == (episode.IndexNumber ?? -1));
|
||||
|
||||
// Prepend a check if the matched episode is on a rewatch cycle and
|
||||
// discard it if the last play date was before the reset date
|
||||
if (matchedEpisode != null && tLastReset != null)
|
||||
{
|
||||
if (DateTime.TryParse(matchedEpisode.LastWatchedAt, out var value) && value < tLastReset)
|
||||
{
|
||||
matchedEpisode = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedEpisode != null)
|
||||
{
|
||||
_logger.LogDebug("Episode is in Watched list {Data}", GetVerboseEpisodeData(episode));
|
||||
|
||||
if (!traktUser.SkipWatchedImportFromTrakt)
|
||||
{
|
||||
DateTime? tLastPlayed = null;
|
||||
if (DateTime.TryParse(matchedEpisode.LastWatchedAt, out var value))
|
||||
{
|
||||
tLastPlayed = value;
|
||||
}
|
||||
|
||||
// Set episode as watched
|
||||
if (!userData.Played)
|
||||
{
|
||||
userData.Played = true;
|
||||
userData.LastPlayedDate = tLastPlayed ?? DateTime.Now;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Keep the highest play count
|
||||
if (userData.PlayCount < matchedEpisode.Plays)
|
||||
{
|
||||
userData.PlayCount = matchedEpisode.Plays;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Update last played if remote time is more recent
|
||||
if (tLastPlayed != null && userData.LastPlayedDate < tLastPlayed)
|
||||
{
|
||||
userData.LastPlayedDate = tLastPlayed;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!traktUser.SkipUnwatchedImportFromTrakt)
|
||||
{
|
||||
userData.Played = false;
|
||||
userData.PlayCount = 0;
|
||||
userData.LastPlayedDate = null;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Only process if changed
|
||||
if (changed)
|
||||
{
|
||||
_userDataManager.SaveUserData(
|
||||
user.Id,
|
||||
episode,
|
||||
userData,
|
||||
UserDataSaveReason.Import,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("No Season match in Watched shows list {Episode}", GetVerboseEpisodeData(episode));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("No Show match in Watched shows list {Episode}", GetVerboseEpisodeData(episode));
|
||||
}
|
||||
|
||||
// Purely for progress reporting
|
||||
currentProgress += percentPerItem;
|
||||
progress.Report(currentProgress);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetVerboseEpisodeData(Episode episode)
|
||||
{
|
||||
var episodeString = new StringBuilder()
|
||||
.Append("Episode: ")
|
||||
.Append(episode.ParentIndexNumber != null ? episode.ParentIndexNumber.ToString() : "null")
|
||||
.Append('x')
|
||||
.Append(episode.IndexNumber != null ? episode.IndexNumber.ToString() : "null")
|
||||
.Append(" '").Append(episode.Name).Append("' ")
|
||||
.Append("Series: '")
|
||||
.Append(episode.Series != null
|
||||
? !string.IsNullOrWhiteSpace(episode.Series.Name)
|
||||
? episode.Series.Name
|
||||
: "null property"
|
||||
: "null class")
|
||||
.Append('\'');
|
||||
|
||||
return episodeString.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a watched match for a series.
|
||||
/// </summary>
|
||||
/// <param name="item">The <see cref="Series"/>.</param>
|
||||
/// <param name="results">IEnumerale of <see cref="TraktShowWatched"/>.</param>
|
||||
/// <returns>TraktShowWatched.</returns>
|
||||
public static TraktShowWatched FindMatch(Series item, IEnumerable<TraktShowWatched> results)
|
||||
{
|
||||
return results.FirstOrDefault(i => IsMatch(item, i.Show));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collected matches for a series.
|
||||
/// </summary>
|
||||
/// <param name="item">The <see cref="Series"/>.</param>
|
||||
/// <param name="results">IEnumerale of <see cref="TraktShowCollected"/>.</param>
|
||||
/// <returns>TraktShowCollected.</returns>
|
||||
public static TraktShowCollected FindMatch(Series item, IEnumerable<TraktShowCollected> results)
|
||||
{
|
||||
return results.FirstOrDefault(i => IsMatch(item, i.Show));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a watched matches for a movie.
|
||||
/// </summary>
|
||||
/// <param name="item">The <see cref="BaseItem"/>.</param>
|
||||
/// <param name="results">IEnumerale of <see cref="TraktMovieWatched"/>.</param>
|
||||
/// <returns>TraktMovieWatched.</returns>
|
||||
public static TraktMovieWatched FindMatch(BaseItem item, IEnumerable<TraktMovieWatched> results)
|
||||
{
|
||||
return results.FirstOrDefault(i => IsMatch(item, i.Movie));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collected matches for a movie.
|
||||
/// </summary>
|
||||
/// <param name="item">The <see cref="BaseItem"/>.</param>
|
||||
/// <param name="results">IEnumerale of <see cref="TraktMovieCollected"/>.</param>
|
||||
/// <returns>TraktMovieCollected.</returns>
|
||||
public static IEnumerable<TraktMovieCollected> FindMatches(BaseItem item, IEnumerable<TraktMovieCollected> results)
|
||||
{
|
||||
return results.Where(i => IsMatch(item, i.Movie)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a <see cref="BaseItem"/> matches a <see cref="TraktMovie"/>.
|
||||
/// </summary>
|
||||
/// <param name="item">The <see cref="BaseItem"/>.</param>
|
||||
/// <param name="movie">The <see cref="TraktMovie"/>.</param>
|
||||
/// <returns><see cref="bool"/> indicating if the <see cref="BaseItem"/> matches a <see cref="TraktMovie"/>.</returns>
|
||||
public static bool IsMatch(BaseItem item, TraktMovie movie)
|
||||
{
|
||||
var imdb = item.GetProviderId(MetadataProvider.Imdb);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(imdb) &&
|
||||
string.Equals(imdb, movie.Ids.Imdb, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
currentProgress += percentPerItem;
|
||||
progress.Report(currentProgress);
|
||||
var tmdb = item.GetProviderId(MetadataProvider.Tmdb);
|
||||
|
||||
if (movie.Ids.Tmdb.HasValue && string.Equals(tmdb, movie.Ids.Tmdb.Value.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.Name == movie.Title && item.ProductionYear == movie.Year)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// _logger.LogInformation(syncItemFailures + " items not parsed");
|
||||
}
|
||||
|
||||
private static string GetVerboseEpisodeData(Episode episode)
|
||||
{
|
||||
var episodeString = new StringBuilder()
|
||||
.Append("Episode: ")
|
||||
.Append(episode.ParentIndexNumber != null ? episode.ParentIndexNumber.ToString() : "null")
|
||||
.Append('x')
|
||||
.Append(episode.IndexNumber != null ? episode.IndexNumber.ToString() : "null")
|
||||
.Append(" '").Append(episode.Name).Append("' ")
|
||||
.Append("Series: '")
|
||||
.Append(episode.Series != null
|
||||
? !string.IsNullOrWhiteSpace(episode.Series.Name)
|
||||
? episode.Series.Name
|
||||
: "null property"
|
||||
: "null class")
|
||||
.Append('\'');
|
||||
|
||||
return episodeString.ToString();
|
||||
}
|
||||
|
||||
public static TraktShowWatched FindMatch(Series item, IEnumerable<TraktShowWatched> results)
|
||||
{
|
||||
return results.FirstOrDefault(i => IsMatch(item, i.Show));
|
||||
}
|
||||
|
||||
public static TraktShowCollected FindMatch(Series item, IEnumerable<TraktShowCollected> results)
|
||||
{
|
||||
return results.FirstOrDefault(i => IsMatch(item, i.Show));
|
||||
}
|
||||
|
||||
public static TraktMovieWatched FindMatch(BaseItem item, IEnumerable<TraktMovieWatched> results)
|
||||
{
|
||||
return results.FirstOrDefault(i => IsMatch(item, i.Movie));
|
||||
}
|
||||
|
||||
public static IEnumerable<TraktMovieCollected> FindMatches(BaseItem item, IEnumerable<TraktMovieCollected> results)
|
||||
{
|
||||
return results.Where(i => IsMatch(item, i.Movie)).ToList();
|
||||
}
|
||||
|
||||
public static bool IsMatch(BaseItem item, TraktMovie movie)
|
||||
{
|
||||
var imdb = item.GetProviderId(MetadataProvider.Imdb);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(imdb) &&
|
||||
string.Equals(imdb, movie.Ids.Imdb, StringComparison.OrdinalIgnoreCase))
|
||||
/// <summary>
|
||||
/// Checks if a <see cref="Series"/> matches a <see cref="TraktShow"/>.
|
||||
/// </summary>
|
||||
/// <param name="item">The <see cref="Series"/>.</param>
|
||||
/// <param name="show">The <see cref="TraktShow"/>.</param>
|
||||
/// <returns><see cref="bool"/> indicating if the <see cref="Series"/> matches a <see cref="TraktShow"/>.</returns>
|
||||
public static bool IsMatch(Series item, TraktShow show)
|
||||
{
|
||||
return true;
|
||||
var tvdb = item.GetProviderId(MetadataProvider.Tvdb);
|
||||
if (!string.IsNullOrWhiteSpace(tvdb) &&
|
||||
string.Equals(tvdb, show.Ids.Tvdb.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var imdb = item.GetProviderId(MetadataProvider.Imdb);
|
||||
if (!string.IsNullOrWhiteSpace(imdb) &&
|
||||
string.Equals(imdb, show.Ids.Imdb, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var tmdb = item.GetProviderId(MetadataProvider.Tmdb);
|
||||
|
||||
if (movie.Ids.Tmdb.HasValue && string.Equals(tmdb, movie.Ids.Tmdb.Value.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.Name == movie.Title && item.ProductionYear == movie.Year)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsMatch(Series item, TraktShow show)
|
||||
{
|
||||
var tvdb = item.GetProviderId(MetadataProvider.Tvdb);
|
||||
if (!string.IsNullOrWhiteSpace(tvdb) &&
|
||||
string.Equals(tvdb, show.Ids.Tvdb.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var imdb = item.GetProviderId(MetadataProvider.Imdb);
|
||||
if (!string.IsNullOrWhiteSpace(imdb) &&
|
||||
string.Equals(imdb, show.Ids.Imdb, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -20,160 +20,335 @@ using Trakt.Api.DataContracts.Sync;
|
||||
using Trakt.Helpers;
|
||||
using Trakt.Model;
|
||||
|
||||
namespace Trakt.ScheduledTasks;
|
||||
|
||||
/// <summary>
|
||||
/// Task that will Sync each users local library with their respective trakt.tv profiles. This task will only include
|
||||
/// titles, watched states will be synced in other tasks.
|
||||
/// </summary>
|
||||
public class SyncLibraryTask : IScheduledTask
|
||||
namespace Trakt.ScheduledTasks
|
||||
{
|
||||
// private readonly IHttpClient _httpClient;
|
||||
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
private readonly ILogger<SyncLibraryTask> _logger;
|
||||
|
||||
private readonly TraktApi _traktApi;
|
||||
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public SyncLibraryTask(
|
||||
ILoggerFactory loggerFactory,
|
||||
IUserManager userManager,
|
||||
IUserDataManager userDataManager,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IServerApplicationHost appHost,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_userDataManager = userDataManager;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = loggerFactory.CreateLogger<SyncLibraryTask>();
|
||||
_traktApi = new TraktApi(loggerFactory.CreateLogger<TraktApi>(), httpClientFactory, appHost, userDataManager, fileSystem);
|
||||
}
|
||||
|
||||
public string Key => "TraktSyncLibraryTask";
|
||||
|
||||
public string Name => "Sync library to trakt.tv";
|
||||
|
||||
public string Category => "Trakt";
|
||||
|
||||
public string Description
|
||||
=> "Adds any media that is in each users trakt monitored locations to their trakt.tv profile";
|
||||
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() => Enumerable.Empty<TaskTriggerInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Gather users and call <see cref="SyncUserLibrary"/>
|
||||
/// Task that will Sync each users local library with their respective trakt.tv profiles. This task will only include
|
||||
/// titles, watched states will be synced in other tasks.
|
||||
/// </summary>
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
public class SyncLibraryTask : IScheduledTask
|
||||
{
|
||||
var users = _userManager.Users.Where(u => UserHelper.GetTraktUser(u) != null).ToList();
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
// No point going further if we don't have users.
|
||||
if (users.Count == 0)
|
||||
private readonly ILogger<SyncLibraryTask> _logger;
|
||||
|
||||
private readonly TraktApi _traktApi;
|
||||
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SyncLibraryTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
|
||||
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||
/// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
|
||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||
public SyncLibraryTask(
|
||||
ILoggerFactory loggerFactory,
|
||||
IUserManager userManager,
|
||||
IUserDataManager userDataManager,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IServerApplicationHost appHost,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger.LogInformation("No Users returned");
|
||||
return;
|
||||
_userManager = userManager;
|
||||
_userDataManager = userDataManager;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = loggerFactory.CreateLogger<SyncLibraryTask>();
|
||||
_traktApi = new TraktApi(loggerFactory.CreateLogger<TraktApi>(), httpClientFactory, appHost, userDataManager, fileSystem);
|
||||
}
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
var traktUser = UserHelper.GetTraktUser(user);
|
||||
/// <inheritdoc />
|
||||
public string Key => "TraktSyncLibraryTask";
|
||||
|
||||
// I'll leave this in here for now, but in reality this continue should never be reached.
|
||||
if (string.IsNullOrEmpty(traktUser?.LinkedMbUserId))
|
||||
/// <inheritdoc />
|
||||
public string Name => "Sync library to trakt.tv";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Category => "Trakt";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description
|
||||
=> "Adds any media that is in each users trakt.tv monitored locations to their trakt.tv profile";
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() => Enumerable.Empty<TaskTriggerInfo>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var users = _userManager.Users.Where(u => UserHelper.GetTraktUser(u) != null).ToList();
|
||||
|
||||
// No point going further if we don't have users.
|
||||
if (users.Count == 0)
|
||||
{
|
||||
_logger.LogError("traktUser is either null or has no linked MB account");
|
||||
continue;
|
||||
_logger.LogInformation("No Users returned");
|
||||
return;
|
||||
}
|
||||
|
||||
await
|
||||
SyncUserLibrary(user, traktUser, progress.Split(users.Count), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
foreach (var user in users)
|
||||
{
|
||||
var traktUser = UserHelper.GetTraktUser(user);
|
||||
|
||||
// I'll leave this in here for now, but in reality this continue should never be reached.
|
||||
if (string.IsNullOrEmpty(traktUser?.LinkedMbUserId))
|
||||
{
|
||||
_logger.LogError("traktUser is either null or has no linked MB account");
|
||||
continue;
|
||||
}
|
||||
|
||||
await
|
||||
SyncUserLibrary(user, traktUser, progress.Split(users.Count), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Count media items and call <see cref="SyncMovies"/> and <see cref="SyncShows"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task SyncUserLibrary(
|
||||
Jellyfin.Data.Entities.User user,
|
||||
TraktUser traktUser,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await SyncMovies(user, traktUser, progress.Split(2), cancellationToken).ConfigureAwait(false);
|
||||
await SyncShows(user, traktUser, progress.Split(2), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sync watched and collected status of <see cref="Movie"/>s with trakt.
|
||||
/// </summary>
|
||||
private async Task SyncMovies(
|
||||
Jellyfin.Data.Entities.User user,
|
||||
TraktUser traktUser,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
/*
|
||||
* In order to sync watched status to trakt.tv we need to know what's been watched on Trakt already. This
|
||||
* will stop us from endlessly incrementing the watched values on the site.
|
||||
*/
|
||||
var traktWatchedMovies = await _traktApi.SendGetAllWatchedMoviesRequest(traktUser).ConfigureAwait(false);
|
||||
var traktCollectedMovies = await _traktApi.SendGetAllCollectedMoviesRequest(traktUser).ConfigureAwait(false);
|
||||
var libraryMovies =
|
||||
_libraryManager.GetItemList(
|
||||
new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { BaseItemKind.Movie },
|
||||
IsVirtualItem = false,
|
||||
OrderBy = new[]
|
||||
{
|
||||
(ItemSortBy.SortName, SortOrder.Ascending)
|
||||
}
|
||||
})
|
||||
.Where(x => _traktApi.CanSync(x, traktUser))
|
||||
.ToList();
|
||||
var collectedMovies = new List<Movie>();
|
||||
var playedMovies = new List<Movie>();
|
||||
var unplayedMovies = new List<Movie>();
|
||||
|
||||
var decisionProgress = progress.Split(4).Split(libraryMovies.Count);
|
||||
foreach (var child in libraryMovies)
|
||||
/// <summary>
|
||||
/// Count media items and call <see cref="SyncMovies"/> and <see cref="SyncShows"/>.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="Jellyfin.Data.Entities.User"/>.</param>
|
||||
/// <param name="traktUser">The <see cref="TraktUser"/>.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task SyncUserLibrary(
|
||||
Jellyfin.Data.Entities.User user,
|
||||
TraktUser traktUser,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var libraryMovie = child as Movie;
|
||||
var userData = _userDataManager.GetUserData(user.Id, child);
|
||||
await SyncMovies(user, traktUser, progress.Split(2), cancellationToken).ConfigureAwait(false);
|
||||
await SyncShows(user, traktUser, progress.Split(2), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task SyncMovies(
|
||||
Jellyfin.Data.Entities.User user,
|
||||
TraktUser traktUser,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
/*
|
||||
* In order to sync watched status to trakt.tv we need to know what's been watched on trakt.tv already. This
|
||||
* will stop us from endlessly incrementing the watched values on the site.
|
||||
*/
|
||||
var traktWatchedMovies = await _traktApi.SendGetAllWatchedMoviesRequest(traktUser).ConfigureAwait(false);
|
||||
var traktCollectedMovies = await _traktApi.SendGetAllCollectedMoviesRequest(traktUser).ConfigureAwait(false);
|
||||
var libraryMovies =
|
||||
_libraryManager.GetItemList(
|
||||
new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { BaseItemKind.Movie },
|
||||
IsVirtualItem = false,
|
||||
OrderBy = new[]
|
||||
{
|
||||
(ItemSortBy.SortName, SortOrder.Ascending)
|
||||
}
|
||||
})
|
||||
.Where(x => _traktApi.CanSync(x, traktUser))
|
||||
.ToList();
|
||||
var collectedMovies = new List<Movie>();
|
||||
var playedMovies = new List<Movie>();
|
||||
var unplayedMovies = new List<Movie>();
|
||||
|
||||
var decisionProgress = progress.Split(4).Split(libraryMovies.Count);
|
||||
foreach (var child in libraryMovies)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var libraryMovie = child as Movie;
|
||||
var userData = _userDataManager.GetUserData(user.Id, child);
|
||||
|
||||
if (traktUser.SynchronizeCollections)
|
||||
{
|
||||
// If movie is not collected, or (export media info setting is enabled and every collected matching movie has different metadata), collect it
|
||||
var collectedMathingMovies = SyncFromTraktTask.FindMatches(libraryMovie, traktCollectedMovies).ToList();
|
||||
if (!collectedMathingMovies.Any()
|
||||
|| (traktUser.ExportMediaInfo
|
||||
&& collectedMathingMovies.All(
|
||||
collectedMovie => collectedMovie.MetadataIsDifferent(libraryMovie))))
|
||||
{
|
||||
collectedMovies.Add(libraryMovie);
|
||||
}
|
||||
}
|
||||
|
||||
var movieWatched = SyncFromTraktTask.FindMatch(libraryMovie, traktWatchedMovies);
|
||||
|
||||
// If the movie has been played locally and is unplayed on trakt.tv then add it to the list
|
||||
if (userData.Played)
|
||||
{
|
||||
if (movieWatched == null)
|
||||
{
|
||||
if (traktUser.PostWatchedHistory)
|
||||
{
|
||||
playedMovies.Add(libraryMovie);
|
||||
}
|
||||
else if (!traktUser.SkipUnwatchedImportFromTrakt)
|
||||
{
|
||||
if (userData.Played)
|
||||
{
|
||||
userData.Played = false;
|
||||
|
||||
_userDataManager.SaveUserData(
|
||||
user.Id,
|
||||
libraryMovie,
|
||||
userData,
|
||||
UserDataSaveReason.Import,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the show has not been played locally but is played on trakt.tv then add it to the unplayed list
|
||||
if (movieWatched != null && traktUser.PostUnwatchedHistory)
|
||||
{
|
||||
unplayedMovies.Add(libraryMovie);
|
||||
}
|
||||
}
|
||||
|
||||
decisionProgress.Report(100);
|
||||
}
|
||||
|
||||
// Send movies to mark collected
|
||||
if (traktUser.SynchronizeCollections)
|
||||
{
|
||||
// if movie is not collected, or (export media info setting is enabled and every collected matching movie has different metadata), collect it
|
||||
var collectedMathingMovies = SyncFromTraktTask.FindMatches(libraryMovie, traktCollectedMovies).ToList();
|
||||
if (!collectedMathingMovies.Any()
|
||||
|| (traktUser.ExportMediaInfo
|
||||
&& collectedMathingMovies.All(
|
||||
collectedMovie => collectedMovie.MetadataIsDifferent(libraryMovie))))
|
||||
{
|
||||
collectedMovies.Add(libraryMovie);
|
||||
}
|
||||
await SendMovieCollectionUpdates(true, traktUser, collectedMovies, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var movieWatched = SyncFromTraktTask.FindMatch(libraryMovie, traktWatchedMovies);
|
||||
// Send movies to mark watched
|
||||
await SendMoviePlaystateUpdates(true, traktUser, playedMovies, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// if the movie has been played locally and is unplayed on trakt.tv then add it to the list
|
||||
if (userData.Played)
|
||||
// Send movies to mark unwatched
|
||||
await SendMoviePlaystateUpdates(false, traktUser, unplayedMovies, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task SendMovieCollectionUpdates(
|
||||
bool collected,
|
||||
TraktUser traktUser,
|
||||
List<Movie> movies,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Movies to {State} Collection {Count}", collected ? "add to" : "remove from", movies.Count);
|
||||
if (movies.Count > 0)
|
||||
{
|
||||
if (movieWatched == null)
|
||||
try
|
||||
{
|
||||
var dataContracts =
|
||||
await _traktApi.SendLibraryUpdateAsync(
|
||||
movies,
|
||||
traktUser,
|
||||
collected ? EventType.Add : EventType.Remove,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (dataContracts != null)
|
||||
{
|
||||
foreach (var traktSyncResponse in dataContracts)
|
||||
{
|
||||
LogTraktResponseDataContract(traktSyncResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ArgumentNullException argNullEx)
|
||||
{
|
||||
_logger.LogError(argNullEx, "ArgumentNullException handled sending movies to trakt.tv");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Exception handled sending movies to trakt.tv");
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendMoviePlaystateUpdates(
|
||||
bool seen,
|
||||
TraktUser traktUser,
|
||||
List<Movie> playedMovies,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Movies to set {State}watched: {Count}", seen ? string.Empty : "un", playedMovies.Count);
|
||||
if (playedMovies.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataContracts =
|
||||
await _traktApi.SendMoviePlaystateUpdates(playedMovies, traktUser, seen, cancellationToken).ConfigureAwait(false);
|
||||
if (dataContracts != null)
|
||||
{
|
||||
foreach (var traktSyncResponse in dataContracts)
|
||||
{
|
||||
LogTraktResponseDataContract(traktSyncResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Error updating movie play states");
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SyncShows(
|
||||
Jellyfin.Data.Entities.User user,
|
||||
TraktUser traktUser,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var traktWatchedShows = await _traktApi.SendGetWatchedShowsRequest(traktUser).ConfigureAwait(false);
|
||||
var traktCollectedShows = await _traktApi.SendGetCollectedShowsRequest(traktUser).ConfigureAwait(false);
|
||||
var episodeItems =
|
||||
_libraryManager.GetItemList(
|
||||
new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { BaseItemKind.Episode },
|
||||
IsVirtualItem = false,
|
||||
OrderBy = new[]
|
||||
{
|
||||
(ItemSortBy.SeriesSortName, SortOrder.Ascending)
|
||||
}
|
||||
})
|
||||
.Where(x => _traktApi.CanSync(x, traktUser))
|
||||
.ToList();
|
||||
|
||||
var collectedEpisodes = new List<Episode>();
|
||||
var playedEpisodes = new List<Episode>();
|
||||
var unplayedEpisodes = new List<Episode>();
|
||||
|
||||
var decisionProgress = progress.Split(4).Split(episodeItems.Count);
|
||||
foreach (var child in episodeItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var episode = child as Episode;
|
||||
var userData = _userDataManager.GetUserData(user.Id, episode);
|
||||
var isPlayedTraktTv = false;
|
||||
var traktWatchedShow = SyncFromTraktTask.FindMatch(episode.Series, traktWatchedShows);
|
||||
|
||||
if (traktWatchedShow?.Seasons != null && traktWatchedShow.Seasons.Count > 0)
|
||||
{
|
||||
isPlayedTraktTv =
|
||||
traktWatchedShow.Seasons.Any(
|
||||
season =>
|
||||
season.Number == episode.GetSeasonNumber() && season.Episodes != null
|
||||
&& season.Episodes.Any(te => te.Number == episode.IndexNumber && te.Plays > 0));
|
||||
}
|
||||
|
||||
// if the show has been played locally and is unplayed on trakt.tv then add it to the list
|
||||
if (userData != null && userData.Played && !isPlayedTraktTv)
|
||||
{
|
||||
if (traktUser.PostWatchedHistory)
|
||||
{
|
||||
playedMovies.Add(libraryMovie);
|
||||
playedEpisodes.Add(episode);
|
||||
}
|
||||
else if (!traktUser.SkipUnwatchedImportFromTrakt)
|
||||
{
|
||||
@ -183,305 +358,140 @@ public class SyncLibraryTask : IScheduledTask
|
||||
|
||||
_userDataManager.SaveUserData(
|
||||
user.Id,
|
||||
libraryMovie,
|
||||
episode,
|
||||
userData,
|
||||
UserDataSaveReason.Import,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the show has not been played locally but is played on trakt.tv then add it to the unplayed list
|
||||
if (movieWatched != null && traktUser.PostUnwatchedHistory)
|
||||
else if (userData != null && !userData.Played && isPlayedTraktTv && traktUser.PostUnwatchedHistory)
|
||||
{
|
||||
unplayedMovies.Add(libraryMovie);
|
||||
// If the show has not been played locally but is played on trakt.tv then add it to the unplayed list
|
||||
unplayedEpisodes.Add(episode);
|
||||
}
|
||||
}
|
||||
|
||||
decisionProgress.Report(100);
|
||||
}
|
||||
|
||||
// send movies to mark collected
|
||||
if (traktUser.SynchronizeCollections)
|
||||
{
|
||||
await SendMovieCollectionUpdates(true, traktUser, collectedMovies, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// send movies to mark watched
|
||||
await SendMoviePlaystateUpdates(true, traktUser, playedMovies, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// send movies to mark unwatched
|
||||
await SendMoviePlaystateUpdates(false, traktUser, unplayedMovies, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task SendMovieCollectionUpdates(
|
||||
bool collected,
|
||||
TraktUser traktUser,
|
||||
List<Movie> movies,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Movies to {State} Collection {Count}", collected ? "add to" : "remove from", movies.Count);
|
||||
if (movies.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataContracts =
|
||||
await _traktApi.SendLibraryUpdateAsync(
|
||||
movies,
|
||||
traktUser,
|
||||
collected ? EventType.Add : EventType.Remove,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (dataContracts != null)
|
||||
if (traktUser.SynchronizeCollections)
|
||||
{
|
||||
foreach (var traktSyncResponse in dataContracts)
|
||||
var traktCollectedShow = SyncFromTraktTask.FindMatch(episode.Series, traktCollectedShows);
|
||||
if (traktCollectedShow?.Seasons == null
|
||||
|| traktCollectedShow.Seasons.All(x => x.Number != episode.ParentIndexNumber)
|
||||
|| traktCollectedShow.Seasons.First(x => x.Number == episode.ParentIndexNumber)
|
||||
.Episodes.All(e => e.Number != episode.IndexNumber))
|
||||
{
|
||||
LogTraktResponseDataContract(traktSyncResponse);
|
||||
collectedEpisodes.Add(episode);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ArgumentNullException argNullEx)
|
||||
{
|
||||
_logger.LogError(argNullEx, "ArgumentNullException handled sending movies to trakt.tv");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Exception handled sending movies to trakt.tv");
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendMoviePlaystateUpdates(
|
||||
bool seen,
|
||||
TraktUser traktUser,
|
||||
List<Movie> playedMovies,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Movies to set {State}watched: {Count}", seen ? string.Empty : "un", playedMovies.Count);
|
||||
if (playedMovies.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataContracts =
|
||||
await _traktApi.SendMoviePlaystateUpdates(playedMovies, traktUser, seen, cancellationToken).ConfigureAwait(false);
|
||||
if (dataContracts != null)
|
||||
{
|
||||
foreach (var traktSyncResponse in dataContracts)
|
||||
{
|
||||
LogTraktResponseDataContract(traktSyncResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Error updating movie play states");
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sync watched and collected status of <see cref="Movie"/>s with trakt.
|
||||
/// </summary>
|
||||
private async Task SyncShows(
|
||||
Jellyfin.Data.Entities.User user,
|
||||
TraktUser traktUser,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var traktWatchedShows = await _traktApi.SendGetWatchedShowsRequest(traktUser).ConfigureAwait(false);
|
||||
var traktCollectedShows = await _traktApi.SendGetCollectedShowsRequest(traktUser).ConfigureAwait(false);
|
||||
var episodeItems =
|
||||
_libraryManager.GetItemList(
|
||||
new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { BaseItemKind.Episode },
|
||||
IsVirtualItem = false,
|
||||
OrderBy = new[]
|
||||
{
|
||||
(ItemSortBy.SeriesSortName, SortOrder.Ascending)
|
||||
}
|
||||
})
|
||||
.Where(x => _traktApi.CanSync(x, traktUser))
|
||||
.ToList();
|
||||
|
||||
var collectedEpisodes = new List<Episode>();
|
||||
var playedEpisodes = new List<Episode>();
|
||||
var unplayedEpisodes = new List<Episode>();
|
||||
|
||||
var decisionProgress = progress.Split(4).Split(episodeItems.Count);
|
||||
foreach (var child in episodeItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var episode = child as Episode;
|
||||
var userData = _userDataManager.GetUserData(user.Id, episode);
|
||||
var isPlayedTraktTv = false;
|
||||
var traktWatchedShow = SyncFromTraktTask.FindMatch(episode.Series, traktWatchedShows);
|
||||
|
||||
if (traktWatchedShow?.Seasons != null && traktWatchedShow.Seasons.Count > 0)
|
||||
{
|
||||
isPlayedTraktTv =
|
||||
traktWatchedShow.Seasons.Any(
|
||||
season =>
|
||||
season.Number == episode.GetSeasonNumber() && season.Episodes != null
|
||||
&& season.Episodes.Any(te => te.Number == episode.IndexNumber && te.Plays > 0));
|
||||
}
|
||||
|
||||
// if the show has been played locally and is unplayed on trakt.tv then add it to the list
|
||||
if (userData != null && userData.Played && !isPlayedTraktTv)
|
||||
{
|
||||
if (traktUser.PostWatchedHistory)
|
||||
{
|
||||
playedEpisodes.Add(episode);
|
||||
}
|
||||
else if (!traktUser.SkipUnwatchedImportFromTrakt)
|
||||
{
|
||||
if (userData.Played)
|
||||
{
|
||||
userData.Played = false;
|
||||
|
||||
_userDataManager.SaveUserData(
|
||||
user.Id,
|
||||
episode,
|
||||
userData,
|
||||
UserDataSaveReason.Import,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (userData != null && !userData.Played && isPlayedTraktTv && traktUser.PostUnwatchedHistory)
|
||||
{
|
||||
// If the show has not been played locally but is played on trakt.tv then add it to the unplayed list
|
||||
unplayedEpisodes.Add(episode);
|
||||
decisionProgress.Report(100);
|
||||
}
|
||||
|
||||
if (traktUser.SynchronizeCollections)
|
||||
{
|
||||
var traktCollectedShow = SyncFromTraktTask.FindMatch(episode.Series, traktCollectedShows);
|
||||
if (traktCollectedShow?.Seasons == null
|
||||
|| traktCollectedShow.Seasons.All(x => x.Number != episode.ParentIndexNumber)
|
||||
|| traktCollectedShow.Seasons.First(x => x.Number == episode.ParentIndexNumber)
|
||||
.Episodes.All(e => e.Number != episode.IndexNumber))
|
||||
{
|
||||
collectedEpisodes.Add(episode);
|
||||
}
|
||||
await SendEpisodeCollectionUpdates(true, traktUser, collectedEpisodes, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
decisionProgress.Report(100);
|
||||
await SendEpisodePlaystateUpdates(true, traktUser, playedEpisodes, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await SendEpisodePlaystateUpdates(false, traktUser, unplayedEpisodes, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (traktUser.SynchronizeCollections)
|
||||
private async Task SendEpisodePlaystateUpdates(
|
||||
bool seen,
|
||||
TraktUser traktUser,
|
||||
List<Episode> playedEpisodes,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await SendEpisodeCollectionUpdates(true, traktUser, collectedEpisodes, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await SendEpisodePlaystateUpdates(true, traktUser, playedEpisodes, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await SendEpisodePlaystateUpdates(false, traktUser, unplayedEpisodes, progress.Split(4), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task SendEpisodePlaystateUpdates(
|
||||
bool seen,
|
||||
TraktUser traktUser,
|
||||
List<Episode> playedEpisodes,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Episodes to set {State}watched: {Count}", seen ? string.Empty : "un", playedEpisodes.Count);
|
||||
if (playedEpisodes.Count > 0)
|
||||
{
|
||||
try
|
||||
_logger.LogInformation("Episodes to set {State}watched: {Count}", seen ? string.Empty : "un", playedEpisodes.Count);
|
||||
if (playedEpisodes.Count > 0)
|
||||
{
|
||||
var dataContracts =
|
||||
await _traktApi.SendEpisodePlaystateUpdates(playedEpisodes, traktUser, seen, cancellationToken).ConfigureAwait(false);
|
||||
if (dataContracts != null)
|
||||
try
|
||||
{
|
||||
foreach (var con in dataContracts)
|
||||
var dataContracts =
|
||||
await _traktApi.SendEpisodePlaystateUpdates(playedEpisodes, traktUser, seen, cancellationToken).ConfigureAwait(false);
|
||||
if (dataContracts != null)
|
||||
{
|
||||
LogTraktResponseDataContract(con);
|
||||
foreach (var con in dataContracts)
|
||||
{
|
||||
LogTraktResponseDataContract(con);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Error updating episode play states");
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendEpisodeCollectionUpdates(
|
||||
bool collected,
|
||||
TraktUser traktUser,
|
||||
List<Episode> collectedEpisodes,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Episodes to add to Collection: {Count}", collectedEpisodes.Count);
|
||||
if (collectedEpisodes.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataContracts =
|
||||
await _traktApi.SendLibraryUpdateAsync(
|
||||
collectedEpisodes,
|
||||
traktUser,
|
||||
collected ? EventType.Add : EventType.Remove,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (dataContracts != null)
|
||||
catch (Exception e)
|
||||
{
|
||||
foreach (var traktSyncResponse in dataContracts)
|
||||
_logger.LogError(e, "Error updating episode play states");
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendEpisodeCollectionUpdates(
|
||||
bool collected,
|
||||
TraktUser traktUser,
|
||||
List<Episode> collectedEpisodes,
|
||||
ISplittableProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Episodes to add to Collection: {Count}", collectedEpisodes.Count);
|
||||
if (collectedEpisodes.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataContracts =
|
||||
await _traktApi.SendLibraryUpdateAsync(
|
||||
collectedEpisodes,
|
||||
traktUser,
|
||||
collected ? EventType.Add : EventType.Remove,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (dataContracts != null)
|
||||
{
|
||||
LogTraktResponseDataContract(traktSyncResponse);
|
||||
foreach (var traktSyncResponse in dataContracts)
|
||||
{
|
||||
LogTraktResponseDataContract(traktSyncResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ArgumentNullException argNullEx)
|
||||
{
|
||||
_logger.LogError(argNullEx, "ArgumentNullException handled sending episodes to trakt.tv");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Exception handled sending episodes to trakt.tv");
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
catch (ArgumentNullException argNullEx)
|
||||
}
|
||||
|
||||
private void LogTraktResponseDataContract(TraktSyncResponse dataContract)
|
||||
{
|
||||
_logger.LogDebug("TraktResponse Added Movies: {Count}", dataContract.Added.Movies);
|
||||
_logger.LogDebug("TraktResponse Added Shows: {Count}", dataContract.Added.Shows);
|
||||
_logger.LogDebug("TraktResponse Added Seasons: {Count}", dataContract.Added.Seasons);
|
||||
_logger.LogDebug("TraktResponse Added Episodes: {Count}", dataContract.Added.Episodes);
|
||||
foreach (var traktMovie in dataContract.NotFound.Movies)
|
||||
{
|
||||
_logger.LogError(argNullEx, "ArgumentNullException handled sending episodes to trakt.tv");
|
||||
_logger.LogError("TraktResponse not Found: {@TraktMovie}", traktMovie);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
foreach (var traktShow in dataContract.NotFound.Shows)
|
||||
{
|
||||
_logger.LogError(e, "Exception handled sending episodes to trakt.tv");
|
||||
_logger.LogError("TraktResponse not Found: {@TraktShow}", traktShow);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
foreach (var traktSeason in dataContract.NotFound.Seasons)
|
||||
{
|
||||
_logger.LogError("TraktResponse not Found: {@TraktSeason}", traktSeason);
|
||||
}
|
||||
|
||||
private void LogTraktResponseDataContract(TraktSyncResponse dataContract)
|
||||
{
|
||||
_logger.LogDebug("TraktResponse Added Movies: {Count}", dataContract.Added.Movies);
|
||||
_logger.LogDebug("TraktResponse Added Shows: {Count}", dataContract.Added.Shows);
|
||||
_logger.LogDebug("TraktResponse Added Seasons: {Count}", dataContract.Added.Seasons);
|
||||
_logger.LogDebug("TraktResponse Added Episodes: {Count}", dataContract.Added.Episodes);
|
||||
foreach (var traktMovie in dataContract.NotFound.Movies)
|
||||
{
|
||||
_logger.LogError("TraktResponse not Found: {@TraktMovie}", traktMovie);
|
||||
}
|
||||
|
||||
foreach (var traktShow in dataContract.NotFound.Shows)
|
||||
{
|
||||
_logger.LogError("TraktResponse not Found: {@TraktShow}", traktShow);
|
||||
}
|
||||
|
||||
foreach (var traktSeason in dataContract.NotFound.Seasons)
|
||||
{
|
||||
_logger.LogError("TraktResponse not Found: {@TraktSeason}", traktSeason);
|
||||
}
|
||||
|
||||
foreach (var traktEpisode in dataContract.NotFound.Episodes)
|
||||
{
|
||||
_logger.LogError("TraktResponse not Found: {@TraktEpisode}", traktEpisode);
|
||||
foreach (var traktEpisode in dataContract.NotFound.Episodes)
|
||||
{
|
||||
_logger.LogError("TraktResponse not Found: {@TraktEpisode}", traktEpisode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace Trakt
|
||||
private Dictionary<string, bool> _playbackPause;
|
||||
|
||||
/// <summary>
|
||||
/// Processes server events.
|
||||
/// Initializes a new instance of the <see cref="ServerMediator"/> class.
|
||||
/// </summary>
|
||||
/// <param name="sessionManager">The <see cref="ISessionManager"/>.</param>
|
||||
/// <param name="userDataManager">The <see cref="IUserDataManager"/>.</param>
|
||||
@ -102,6 +102,7 @@ namespace Trakt
|
||||
/// <summary>
|
||||
/// Run observer tasks for observed events.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public Task RunAsync()
|
||||
{
|
||||
_userDataManager.UserDataSaved += OnUserDataSaved;
|
||||
@ -191,7 +192,7 @@ namespace Trakt
|
||||
|
||||
if (!_traktApi.CanSync(playbackProgressEventArgs.Item, traktUser))
|
||||
{
|
||||
_logger.LogDebug("Syncing playback for {Item} is forbidden for user {User}.", playbackProgressEventArgs.Item.Name, user.Username);
|
||||
_logger.LogDebug("Syncing playback for {Item} is forbidden for user {User}.", playbackProgressEventArgs.Item.Path, user.Username);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -199,7 +200,7 @@ namespace Trakt
|
||||
var progressPercent = video.RunTimeTicks.HasValue && video.RunTimeTicks != 0 ?
|
||||
(float)(playbackProgressEventArgs.PlaybackPositionTicks ?? 0) / video.RunTimeTicks.Value * 100.0f : 0.0f;
|
||||
|
||||
_logger.LogDebug("User {User} started watching item {Item}.", user.Username, playbackProgressEventArgs.Item.Name);
|
||||
_logger.LogDebug("User {User} started watching item {Item}.", user.Username, playbackProgressEventArgs.Item.Path);
|
||||
|
||||
try
|
||||
{
|
||||
@ -266,7 +267,7 @@ namespace Trakt
|
||||
|
||||
if (!_traktApi.CanSync(playbackProgressEventArgs.Item, traktUser))
|
||||
{
|
||||
_logger.LogDebug("Syncing playback for {Item} is forbidden for user {User}.", playbackProgressEventArgs.Item.Name, user.Username);
|
||||
_logger.LogDebug("Syncing playback for {Item} is forbidden for user {User}.", playbackProgressEventArgs.Item.Path, user.Username);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -274,7 +275,7 @@ namespace Trakt
|
||||
var progressPercent = video.RunTimeTicks.HasValue && video.RunTimeTicks != 0 ?
|
||||
(float)(playbackProgressEventArgs.PlaybackPositionTicks ?? 0) / video.RunTimeTicks.Value * 100.0f : 0.0f;
|
||||
|
||||
_logger.LogDebug("User {User} progressed watching item {Item}.", user.Username, playbackProgressEventArgs.Item.Name);
|
||||
_logger.LogDebug("User {User} progressed watching item {Item}.", user.Username, playbackProgressEventArgs.Item.Path);
|
||||
|
||||
try
|
||||
{
|
||||
@ -344,8 +345,8 @@ namespace Trakt
|
||||
/// Media playback has stopped.
|
||||
/// Depending on playback progress, let trakt.tv know the user has completed watching the item.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="playbackStoppedEventArgs"></param>
|
||||
/// <param name="sender">The sending entity.</param>
|
||||
/// <param name="playbackStoppedEventArgs">The <see cref="PlaybackStopEventArgs"/>.</param>
|
||||
private async void KernelPlaybackStopped(object sender, PlaybackStopEventArgs playbackStoppedEventArgs)
|
||||
{
|
||||
if (playbackStoppedEventArgs.Users == null || !playbackStoppedEventArgs.Users.Any() || playbackStoppedEventArgs.Item == null)
|
||||
@ -441,7 +442,7 @@ namespace Trakt
|
||||
/// <summary>
|
||||
/// Removes event subscriptions on dispose.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see cref="bool"/> indicating if object is currently disposed</param>
|
||||
/// <param name="disposing"><see cref="bool"/> indicating if object is currently disposed.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
|
@ -9,8 +9,6 @@
|
||||
<Nullable>disable</Nullable>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
<!-- Suppress all xmldoc warnings -->
|
||||
<NoWarn>CS1591,CS1572,CS1573,CS1574,SA1629,SA1606,SA1611,SA1614,SA1615,SA1616,SA1642</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -25,7 +23,7 @@
|
||||
<PackageReference Include="Jellyfin.Controller" Version="10.*-*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<div id="traktConfigurationPage" data-role="page" class="page type-interior pluginConfigurationPage traktConfigurationPage" data-controller="__plugin/traktjs">
|
||||
<div id="traktConfigurationPage" data-role="page"
|
||||
class="page type-interior pluginConfigurationPage traktConfigurationPage" data-controller="__plugin/traktjs">
|
||||
<div data-role="content">
|
||||
<div class="content-primary">
|
||||
|
||||
@ -12,15 +13,18 @@
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectUser" name="selectUser" label="Configure Trakt for:"></select>
|
||||
<select is="emby-select" id="selectUser" name="selectUser" label="Configure trakt.tv for:"></select>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fieldDescription hide" id="authorizedDescription">
|
||||
This user is authorized. You can force a re-authorization by clicking the button below.
|
||||
</div>
|
||||
<button is="emby-button" type="button" id="authorizeDevice" class="raised block">Authorize device</button>
|
||||
<button is="emby-button" type="button" id="authorizeDevice" class="raised block">Authorize
|
||||
device</button>
|
||||
<div id="activateWithCode" class="hide">
|
||||
Please visit <a href="https://trakt.tv/activate" class="button-link emby-button" target="_blank">https://trakt.tv/activate</a> and authorize Jellyfin to access your account.<br />
|
||||
Please visit <a href="https://trakt.tv/activate" class="button-link emby-button"
|
||||
target="_blank">https://trakt.tv/activate</a> and authorize Jellyfin to access your
|
||||
account.<br />
|
||||
Your device code is <span id="userCode"></span>.
|
||||
</div>
|
||||
</div>
|
||||
@ -32,56 +36,62 @@
|
||||
<br />
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkSkipUnwatchedImportFromTrakt" name="chkSkipUnwatchedImportFromTrakt" />
|
||||
<input is="emby-checkbox" type="checkbox" id="chkSkipUnwatchedImportFromTrakt"
|
||||
name="chkSkipUnwatchedImportFromTrakt" />
|
||||
<span>Skip unwatched import from Trakt</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">
|
||||
The Import from Trakt scheduled task will set local items unwatched if item is marked as unwached on Trakt. If checked, do not import unwatched status.
|
||||
The Import from trakt.tv scheduled task will set local items unwatched if item is marked as
|
||||
unwached on Trakt. If checked, do not import unwatched status.
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkSkipWatchedImportFromTrakt" name="chkSkipWatchedImportFromTrakt" />
|
||||
<input is="emby-checkbox" type="checkbox" id="chkSkipWatchedImportFromTrakt"
|
||||
name="chkSkipWatchedImportFromTrakt" />
|
||||
<span>Skip watched import from Trakt</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">
|
||||
The Import from Trakt scheduled task will set local items as watched if item is marked as watched on Trakt. If checked, do not import watched status.
|
||||
The Import from trakt.tv scheduled task will set local items as watched if item is marked as
|
||||
watched on Trakt. If checked, do not import watched status.
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkPostWatchedHistory" name="chkPostWatchedHistory" />
|
||||
<span>During Scheduled Task, set Trakt items to watched if local item is watched</span>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkPostWatchedHistory"
|
||||
name="chkPostWatchedHistory" />
|
||||
<span>During Scheduled Task, set trakt.tv items to watched if local item is watched</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">
|
||||
Controls what is synced to Trakt when when scheduled task is run.
|
||||
Controls what is synced to trakt.tv when when scheduled task is run.
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkPostUnwatchedHistory" name="chkPostUnwatchedHistory" />
|
||||
<span>During Scheduled Task, set Trakt items to unwatched if local item is unwatched</span>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkPostUnwatchedHistory"
|
||||
name="chkPostUnwatchedHistory" />
|
||||
<span>During Scheduled Task, set trakt.tv items to unwatched if local item is unwatched</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">
|
||||
Controls what is synced to Trakt when when scheduled task is run.
|
||||
Controls what is synced to trakt.tv when when scheduled task is run.
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkPostSetWatched" name="chkPostSetWatched" />
|
||||
<span>Set Trakt item to Watched when local item is changed to Watched.</span>
|
||||
<span>Set trakt.tv item to Watched when local item is changed to Watched.</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">
|
||||
Controls what is synced to Trakt when item statuses are changed during normal use.
|
||||
Controls what is synced to trakt.tv when item statuses are changed during normal use.
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkPostSetUnwatched" name="chkPostSetUnwatched" />
|
||||
<span>Set Trakt item to Unwatched when local item is changed to Unwatched.</span>
|
||||
<span>Set trakt.tv item to Unwatched when local item is changed to Unwatched.</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">
|
||||
Controls what is synced to Trakt when item statuses are changed during normal use.
|
||||
Controls what is synced to trakt.tv when item statuses are changed during normal use.
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
@ -90,7 +100,7 @@
|
||||
<span>Enable debug logging</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">
|
||||
When enabled, all data sent to trakt is logged.
|
||||
When enabled, all data sent to trakt.tv is logged.
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
@ -129,4 +139,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -6,7 +6,7 @@ const TraktConfigurationPage = {
|
||||
return curr.LinkedMbUserId == userId;
|
||||
//return true;
|
||||
})[0];
|
||||
// User doesn't have a config, so create a default one.
|
||||
// User doesn't have a config, so create a default one.
|
||||
if (!currentUserConfig) {
|
||||
// You don't have to put every property in here, just the ones the UI is expecting (below)
|
||||
currentUserConfig = {
|
||||
@ -165,7 +165,7 @@ export default function (view) {
|
||||
});
|
||||
}
|
||||
ApiClient.fetch(request).then(function (result) {
|
||||
console.log('Trakt user code: ' + result.userCode);
|
||||
console.log('trakt.tv user code: ' + result.userCode);
|
||||
view.querySelector('#authorizedDescription').classList.add('hide');
|
||||
view.querySelector('#authorizeDevice').classList.add('hide');
|
||||
view.querySelector('#userCode').textContent = result.userCode;
|
||||
@ -182,7 +182,7 @@ export default function (view) {
|
||||
}).catch(handleError);
|
||||
});
|
||||
|
||||
view.addEventListener('viewshow', function() {
|
||||
view.addEventListener('viewshow', function () {
|
||||
const page = this;
|
||||
ApiClient.getUsers().then(function (users) {
|
||||
TraktConfigurationPage.populateUsers(users);
|
||||
|
@ -1,6 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RuleSet Name="Rules for Jellyfin.Server" Description="Code analysis rules for Jellyfin.Server.csproj" ToolsVersion="14.0">
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
|
||||
<!-- error on SA1000: The keyword 'new' should be followed by a space -->
|
||||
<Rule Id="SA1000" Action="Error" />
|
||||
<!-- error on SA1001: Commas should not be preceded by whitespace -->
|
||||
<Rule Id="SA1001" Action="Error" />
|
||||
<!-- error on SA1117: The parameters should all be placed on the same line or each parameter should be placed on its own line -->
|
||||
<Rule Id="SA1117" Action="Error" />
|
||||
<!-- error on SA1142: Refer to tuple fields by name -->
|
||||
<Rule Id="SA1142" Action="Error" />
|
||||
<!-- error on SA1210: Using directives should be ordered alphabetically by the namespaces -->
|
||||
<Rule Id="SA1210" Action="Error" />
|
||||
<!-- error on SA1316: Tuple element names should use correct casing -->
|
||||
<Rule Id="SA1316" Action="Error" />
|
||||
<!-- error on SA1414: Tuple types in signatures should have element names -->
|
||||
<Rule Id="SA1414" Action="Error" />
|
||||
<!-- error on SA1518: File is required to end with a single newline character -->
|
||||
<Rule Id="SA1518" Action="Error" />
|
||||
<!-- error on SA1629: Documentation text should end with a period -->
|
||||
<Rule Id="SA1629" Action="Error" />
|
||||
|
||||
<!-- disable warning SA1009: Closing parenthesis should be followed by a space. -->
|
||||
<Rule Id="SA1009" Action="None" />
|
||||
<!-- disable warning SA1011: Closing square bracket should be followed by a space. -->
|
||||
@ -38,6 +57,10 @@
|
||||
</Rules>
|
||||
|
||||
<Rules AnalyzerId="Microsoft.CodeAnalysis.NetAnalyzers" RuleNamespace="Microsoft.Design">
|
||||
<!-- error on CA1001: Types that own disposable fields should be disposable -->
|
||||
<Rule Id="CA1001" Action="Error" />
|
||||
<!-- error on CA1012: Abstract types should not have public constructors -->
|
||||
<Rule Id="CA1012" Action="Error" />
|
||||
<!-- error on CA1063: Implement IDisposable correctly -->
|
||||
<Rule Id="CA1063" Action="Error" />
|
||||
<!-- error on CA1305: Specify IFormatProvider -->
|
||||
@ -56,9 +79,13 @@
|
||||
<Rule Id="CA1843" Action="Error" />
|
||||
<!-- error on CA1845: Use span-based 'string.Concat' -->
|
||||
<Rule Id="CA1845" Action="Error" />
|
||||
<!-- error on CA1849: Call async methods when in an async method -->
|
||||
<Rule Id="CA1849" Action="Error" />
|
||||
<!-- error on CA2016: Forward the CancellationToken parameter to methods that take one
|
||||
or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token -->
|
||||
<Rule Id="CA2016" Action="Error" />
|
||||
<!-- error on CA2215: Dispose methods should call base class dispose -->
|
||||
<Rule Id="CA2215" Action="Error" />
|
||||
<!-- error on CA2254: Template should be a static expression -->
|
||||
<Rule Id="CA2254" Action="Error" />
|
||||
|
||||
@ -115,4 +142,9 @@
|
||||
<!-- disable warning CA2234: Pass System.Uri objects instead of strings -->
|
||||
<Rule Id="CA2234" Action="None" />
|
||||
</Rules>
|
||||
|
||||
<Rules AnalyzerId="Microsoft.CodeAnalysis.BannedApiAnalyzers" RuleNamespace="Microsoft.Design">
|
||||
<!-- error on RS0030: Do not used banned APIs -->
|
||||
<Rule Id="RS0030" Action="Error" />
|
||||
</Rules>
|
||||
</RuleSet>
|
||||
|
Loading…
Reference in New Issue
Block a user