mirror of
https://github.com/jellyfin/jellyfin-plugin-trakt.git
synced 2024-11-26 23:30:25 +00:00
initial
This commit is contained in:
commit
9bb361922a
22
.gitattributes
vendored
Normal file
22
.gitattributes
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
*.sln merge=union
|
||||
*.csproj merge=union
|
||||
*.vbproj merge=union
|
||||
*.fsproj merge=union
|
||||
*.dbproj merge=union
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
230
.gitignore
vendored
Normal file
230
.gitignore
vendored
Normal file
@ -0,0 +1,230 @@
|
||||
#################
|
||||
## Eclipse
|
||||
#################
|
||||
|
||||
*.pydevproject
|
||||
.project
|
||||
.metadata
|
||||
bin/
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
#################
|
||||
## Media Browser
|
||||
#################
|
||||
ProgramData*/
|
||||
ProgramData-Server*/
|
||||
ProgramData-UI*/
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
#################
|
||||
|
||||
.vs
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
build/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.log
|
||||
*.scc
|
||||
*.scc
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.orig
|
||||
*.rej
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.ipch
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
*.pubxml
|
||||
|
||||
# NuGet Packages Directory
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
packages/
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
sql/
|
||||
*.Cache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.[Pp]ublish.xml
|
||||
*.publishsettings
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
App_Data/*.mdf
|
||||
App_Data/*.ldf
|
||||
|
||||
#############
|
||||
## Windows detritus
|
||||
#############
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Mac crap
|
||||
.DS_Store
|
||||
|
||||
|
||||
#############
|
||||
## Python
|
||||
#############
|
||||
|
||||
*.py[co]
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist/
|
||||
build/
|
||||
eggs/
|
||||
parts/
|
||||
var/
|
||||
sdist/
|
||||
develop-eggs/
|
||||
.installed.cfg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
|
||||
#Translations
|
||||
*.mo
|
||||
|
||||
#Mr Developer
|
||||
.mr.developer.cfg
|
6
.nuget/NuGet.Config
Normal file
6
.nuget/NuGet.Config
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<solution>
|
||||
<add key="disableSourceControlIntegration" value="true" />
|
||||
</solution>
|
||||
</configuration>
|
BIN
.nuget/NuGet.exe
Normal file
BIN
.nuget/NuGet.exe
Normal file
Binary file not shown.
153
.nuget/NuGet.targets
Normal file
153
.nuget/NuGet.targets
Normal file
@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
|
||||
|
||||
<!-- Enable the restore command to run before builds -->
|
||||
<RestorePackages Condition=" '$(RestorePackages)' == '' ">false</RestorePackages>
|
||||
|
||||
<!-- Property that enables building a package from a project -->
|
||||
<BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>
|
||||
|
||||
<!-- Determines if package restore consent is required to restore packages -->
|
||||
<RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>
|
||||
|
||||
<!-- Download NuGet.exe if it does not already exist -->
|
||||
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(PackageSources)' == '' ">
|
||||
<!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
|
||||
<!-- The official NuGet package source (https://nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
|
||||
<!--
|
||||
<PackageSource Include="https://nuget.org/api/v2/" />
|
||||
<PackageSource Include="https://my-nuget-source/nuget/" />
|
||||
-->
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
|
||||
<!-- Windows specific commands -->
|
||||
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
|
||||
<PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
|
||||
<PackagesDir>$([System.IO.Path]::Combine($(SolutionDir), "packages"))</PackagesDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
|
||||
<!-- We need to launch nuget.exe with the mono command if we're not on windows -->
|
||||
<NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
|
||||
<PackagesConfig>packages.config</PackagesConfig>
|
||||
<PackagesDir>$(SolutionDir)packages</PackagesDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- NuGet command -->
|
||||
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\nuget.exe</NuGetExePath>
|
||||
<PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>
|
||||
|
||||
<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
|
||||
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 $(NuGetExePath)</NuGetCommand>
|
||||
|
||||
<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
|
||||
|
||||
<RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
|
||||
<!-- Commands -->
|
||||
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -o "$(PackagesDir)"</RestoreCommand>
|
||||
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols</BuildCommand>
|
||||
|
||||
<!-- We need to ensure packages are restored prior to assembly resolve -->
|
||||
<ResolveReferencesDependsOn Condition="$(RestorePackages) == 'true'">
|
||||
RestorePackages;
|
||||
$(ResolveReferencesDependsOn);
|
||||
</ResolveReferencesDependsOn>
|
||||
|
||||
<!-- Make the build depend on restore packages -->
|
||||
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
|
||||
$(BuildDependsOn);
|
||||
BuildPackage;
|
||||
</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="CheckPrerequisites">
|
||||
<!-- Raise an error if we're unable to locate nuget.exe -->
|
||||
<Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
|
||||
<SetEnvironmentVariable EnvKey="VisualStudioVersion" EnvValue="$(VisualStudioVersion)" Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' " />
|
||||
<!--
|
||||
Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
|
||||
This effectively acts as a lock that makes sure that the download operation will only happen once and all
|
||||
parallel builds will have to wait for it to complete.
|
||||
-->
|
||||
<MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_DownloadNuGet">
|
||||
<DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
|
||||
<Exec Command="$(RestoreCommand)"
|
||||
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />
|
||||
|
||||
<Exec Command="$(RestoreCommand)"
|
||||
LogStandardErrorAsError="true"
|
||||
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
|
||||
<Exec Command="$(BuildCommand)"
|
||||
Condition=" '$(OS)' != 'Windows_NT' " />
|
||||
|
||||
<Exec Command="$(BuildCommand)"
|
||||
LogStandardErrorAsError="true"
|
||||
Condition=" '$(OS)' == 'Windows_NT' " />
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
|
||||
<ParameterGroup>
|
||||
<OutputFilename ParameterType="System.String" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Reference Include="System.Core" />
|
||||
<Using Namespace="System" />
|
||||
<Using Namespace="System.IO" />
|
||||
<Using Namespace="System.Net" />
|
||||
<Using Namespace="Microsoft.Build.Framework" />
|
||||
<Using Namespace="Microsoft.Build.Utilities" />
|
||||
<Code Type="Fragment" Language="cs">
|
||||
<![CDATA[
|
||||
try {
|
||||
OutputFilename = Path.GetFullPath(OutputFilename);
|
||||
|
||||
Log.LogMessage("Downloading latest version of NuGet.exe...");
|
||||
WebClient webClient = new WebClient();
|
||||
webClient.DownloadFile("https://nuget.org/nuget.exe", OutputFilename);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.LogErrorFromException(ex);
|
||||
return false;
|
||||
}
|
||||
]]>
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
|
||||
<UsingTask TaskName="SetEnvironmentVariable" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
|
||||
<ParameterGroup>
|
||||
<EnvKey ParameterType="System.String" Required="true" />
|
||||
<EnvValue ParameterType="System.String" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Using Namespace="System" />
|
||||
<Code Type="Fragment" Language="cs">
|
||||
<![CDATA[
|
||||
try {
|
||||
Environment.SetEnvironmentVariable(EnvKey, EnvValue, System.EnvironmentVariableTarget.Process);
|
||||
}
|
||||
catch {
|
||||
}
|
||||
]]>
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
</Project>
|
22
LICENSE.md
Normal file
22
LICENSE.md
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) Media Browser http://mediabrowser.tv
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
Emby.Plugins
|
||||
====================
|
||||
|
||||
This repository contains many of the plugins that are built and maintained by the Emby community.
|
||||
|
||||
Each of the projects has a build event that copies it's output to the programdata/plugins folder.
|
||||
|
||||
By default this assumes you have the server repository side by side in a folder called 'MediaBrowser'. If this is not the case, or if you've installed the server than you'll need to update the build events manually in order to test code changes.
|
||||
|
||||
|
||||
## More Information ##
|
||||
|
||||
[How to Build a Server Plugin](https://github.com/MediaBrowser/MediaBrowser/wiki/How-to-build-a-Server-Plugin)
|
3
SharedVersion.cs
Normal file
3
SharedVersion.cs
Normal file
@ -0,0 +1,3 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("3.0.*")]
|
31
Trakt.sln
Normal file
31
Trakt.sln
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F17571BB-66D4-4DB5-8EC0-33A76DD5B018}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
SharedVersion.cs = SharedVersion.cs
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trakt", "Trakt\Trakt.csproj", "{7FFC306B-2680-49C7-8BE0-6358B2A8A409}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7FFC306B-2680-49C7-8BE0-6358B2A8A409}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7FFC306B-2680-49C7-8BE0-6358B2A8A409}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7FFC306B-2680-49C7-8BE0-6358B2A8A409}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7FFC306B-2680-49C7-8BE0-6358B2A8A409}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7FFC306B-2680-49C7-8BE0-6358B2A8A409}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7FFC306B-2680-49C7-8BE0-6358B2A8A409}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
20
Trakt/Api/DataContracts/BaseModel/TraktEpisode.cs
Normal file
20
Trakt/Api/DataContracts/BaseModel/TraktEpisode.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktEpisode
|
||||
{
|
||||
[DataMember(Name = "season", EmitDefaultValue = false)]
|
||||
public int? Season { get; set; }
|
||||
|
||||
[DataMember(Name = "number", EmitDefaultValue = false)]
|
||||
public int? Number { get; set; }
|
||||
|
||||
[DataMember(Name = "title", EmitDefaultValue = false)]
|
||||
public string Title { get; set; }
|
||||
|
||||
[DataMember(Name = "ids")]
|
||||
public TraktEpisodeId Ids { get; set; }
|
||||
}
|
||||
}
|
20
Trakt/Api/DataContracts/BaseModel/TraktEpisodeId.cs
Normal file
20
Trakt/Api/DataContracts/BaseModel/TraktEpisodeId.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktEpisodeId : TraktId
|
||||
{
|
||||
[DataMember(Name = "imdb", EmitDefaultValue = false)]
|
||||
public string Imdb { get; set; }
|
||||
|
||||
[DataMember(Name = "tmdb", EmitDefaultValue = false)]
|
||||
public int? Tmdb { get; set; }
|
||||
|
||||
[DataMember(Name = "tvdb", EmitDefaultValue = false)]
|
||||
public int? Tvdb { get; set; }
|
||||
|
||||
[DataMember(Name = "tvrage", EmitDefaultValue = false)]
|
||||
public int? TvRage { get; set; }
|
||||
}
|
||||
}
|
14
Trakt/Api/DataContracts/BaseModel/TraktId.cs
Normal file
14
Trakt/Api/DataContracts/BaseModel/TraktId.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktId
|
||||
{
|
||||
[DataMember(Name = "trakt", EmitDefaultValue = false)]
|
||||
public int? Trakt { get; set; }
|
||||
|
||||
[DataMember(Name = "slug", EmitDefaultValue = false)]
|
||||
public string Slug { get; set; }
|
||||
}
|
||||
}
|
17
Trakt/Api/DataContracts/BaseModel/TraktMovie.cs
Normal file
17
Trakt/Api/DataContracts/BaseModel/TraktMovie.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktMovie
|
||||
{
|
||||
[DataMember(Name = "title", EmitDefaultValue = false)]
|
||||
public string Title { get; set; }
|
||||
|
||||
[DataMember(Name = "year", EmitDefaultValue = false)]
|
||||
public int? Year { get; set; }
|
||||
|
||||
[DataMember(Name = "ids")]
|
||||
public TraktMovieId Ids { get; set; }
|
||||
}
|
||||
}
|
14
Trakt/Api/DataContracts/BaseModel/TraktMovieId.cs
Normal file
14
Trakt/Api/DataContracts/BaseModel/TraktMovieId.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktMovieId : TraktId
|
||||
{
|
||||
[DataMember(Name = "imdb", EmitDefaultValue = false)]
|
||||
public string Imdb { get; set; }
|
||||
|
||||
[DataMember(Name = "tmdb", EmitDefaultValue = false)]
|
||||
public int? Tmdb { get; set; }
|
||||
}
|
||||
}
|
14
Trakt/Api/DataContracts/BaseModel/TraktPerson.cs
Normal file
14
Trakt/Api/DataContracts/BaseModel/TraktPerson.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktPerson
|
||||
{
|
||||
[DataMember(Name = "name", EmitDefaultValue = false)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember(Name = "ids")]
|
||||
public TraktPersonId Ids { get; set; }
|
||||
}
|
||||
}
|
17
Trakt/Api/DataContracts/BaseModel/TraktPersonId.cs
Normal file
17
Trakt/Api/DataContracts/BaseModel/TraktPersonId.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktPersonId : TraktId
|
||||
{
|
||||
[DataMember(Name = "imdb")]
|
||||
public string ImdbId { get; set; }
|
||||
|
||||
[DataMember(Name = "tmdb")]
|
||||
public int? TmdbId { get; set; }
|
||||
|
||||
[DataMember(Name = "tvrage")]
|
||||
public int? TvRageId { get; set; }
|
||||
}
|
||||
}
|
14
Trakt/Api/DataContracts/BaseModel/TraktRated.cs
Normal file
14
Trakt/Api/DataContracts/BaseModel/TraktRated.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public abstract class TraktRated
|
||||
{
|
||||
[DataMember(Name = "rating")]
|
||||
public int? Rating { get; set; }
|
||||
|
||||
[DataMember(Name = "rated_at")]
|
||||
public string RatedAt { get; set; }
|
||||
}
|
||||
}
|
14
Trakt/Api/DataContracts/BaseModel/TraktSeason.cs
Normal file
14
Trakt/Api/DataContracts/BaseModel/TraktSeason.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktSeason
|
||||
{
|
||||
[DataMember(Name = "number", EmitDefaultValue = false)]
|
||||
public int? Number { get; set; }
|
||||
|
||||
[DataMember(Name = "ids")]
|
||||
public TraktSeasonId Ids { get; set; }
|
||||
}
|
||||
}
|
17
Trakt/Api/DataContracts/BaseModel/TraktSeasonId.cs
Normal file
17
Trakt/Api/DataContracts/BaseModel/TraktSeasonId.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktSeasonId : TraktId
|
||||
{
|
||||
[DataMember(Name = "tmdb")]
|
||||
public int? Tmdb { get; set; }
|
||||
|
||||
[DataMember(Name = "tvdb")]
|
||||
public int? Tvdb { get; set; }
|
||||
|
||||
[DataMember(Name = "tvrage")]
|
||||
public int? TvRage { get; set; }
|
||||
}
|
||||
}
|
17
Trakt/Api/DataContracts/BaseModel/TraktShow.cs
Normal file
17
Trakt/Api/DataContracts/BaseModel/TraktShow.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktShow
|
||||
{
|
||||
[DataMember(Name = "title", EmitDefaultValue = false)]
|
||||
public string Title { get; set; }
|
||||
|
||||
[DataMember(Name = "year", EmitDefaultValue = false)]
|
||||
public int? Year { get; set; }
|
||||
|
||||
[DataMember(Name = "ids")]
|
||||
public TraktShowId Ids { get; set; }
|
||||
}
|
||||
}
|
20
Trakt/Api/DataContracts/BaseModel/TraktShowId.cs
Normal file
20
Trakt/Api/DataContracts/BaseModel/TraktShowId.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktShowId : TraktId
|
||||
{
|
||||
[DataMember(Name = "imdb", EmitDefaultValue = false)]
|
||||
public string Imdb { get; set; }
|
||||
|
||||
[DataMember(Name = "tmdb", EmitDefaultValue = false)]
|
||||
public int? Tmdb { get; set; }
|
||||
|
||||
[DataMember(Name = "tvdb", EmitDefaultValue = false)]
|
||||
public int? Tvdb { get; set; }
|
||||
|
||||
[DataMember(Name = "tvrage", EmitDefaultValue = false)]
|
||||
public int? TvRage { get; set; }
|
||||
}
|
||||
}
|
20
Trakt/Api/DataContracts/BaseModel/TraktUserSummary.cs
Normal file
20
Trakt/Api/DataContracts/BaseModel/TraktUserSummary.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.BaseModel
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktUserSummary
|
||||
{
|
||||
[DataMember(Name = "username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[DataMember(Name = "name")]
|
||||
public string FullName { get; set; }
|
||||
|
||||
[DataMember(Name = "vip")]
|
||||
public bool IsVip { get; set; }
|
||||
|
||||
[DataMember(Name = "private")]
|
||||
public bool IsPrivate { get; set; }
|
||||
}
|
||||
}
|
39
Trakt/Api/DataContracts/Comments/TraktComment.cs
Normal file
39
Trakt/Api/DataContracts/Comments/TraktComment.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Comments
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktComment
|
||||
{
|
||||
[DataMember(Name = "id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember(Name = "parent_id")]
|
||||
public int? ParentId { get; set; }
|
||||
|
||||
[DataMember(Name = "created_at")]
|
||||
public string CreatedAt { get; set; }
|
||||
|
||||
[DataMember(Name = "comment")]
|
||||
public string Comment { get; set; }
|
||||
|
||||
[DataMember(Name = "spoiler")]
|
||||
public bool IsSpoiler { get; set; }
|
||||
|
||||
[DataMember(Name = "review")]
|
||||
public bool IsReview { get; set; }
|
||||
|
||||
[DataMember(Name = "replies")]
|
||||
public int Replies { get; set; }
|
||||
|
||||
[DataMember(Name = "likes")]
|
||||
public int Likes { get; set; }
|
||||
|
||||
[DataMember(Name = "user_rating")]
|
||||
public int? UserRating { get; set; }
|
||||
|
||||
[DataMember(Name = "user")]
|
||||
public TraktUserSummary User { get; set; }
|
||||
}
|
||||
}
|
24
Trakt/Api/DataContracts/Scrobble/TraktScrobbleEpisode.cs
Normal file
24
Trakt/Api/DataContracts/Scrobble/TraktScrobbleEpisode.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Scrobble
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktScrobbleEpisode
|
||||
{
|
||||
[DataMember(Name = "show", EmitDefaultValue = false)]
|
||||
public TraktShow Show { get; set; }
|
||||
|
||||
[DataMember(Name = "episode")]
|
||||
public TraktEpisode Episode { get; set; }
|
||||
|
||||
[DataMember(Name = "progress")]
|
||||
public float Progress { get; set; }
|
||||
|
||||
[DataMember(Name = "app_version")]
|
||||
public string AppVersion { get; set; }
|
||||
|
||||
[DataMember(Name = "app_date")]
|
||||
public string AppDate { get; set; }
|
||||
}
|
||||
}
|
21
Trakt/Api/DataContracts/Scrobble/TraktScrobbleMovie.cs
Normal file
21
Trakt/Api/DataContracts/Scrobble/TraktScrobbleMovie.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Scrobble
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktScrobbleMovie
|
||||
{
|
||||
[DataMember(Name = "movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
|
||||
[DataMember(Name = "progress")]
|
||||
public float Progress { get; set; }
|
||||
|
||||
[DataMember(Name = "app_version")]
|
||||
public string AppVersion { get; set; }
|
||||
|
||||
[DataMember(Name = "app_date")]
|
||||
public string AppDate { get; set; }
|
||||
}
|
||||
}
|
40
Trakt/Api/DataContracts/Scrobble/TraktScrobbleResponse.cs
Normal file
40
Trakt/Api/DataContracts/Scrobble/TraktScrobbleResponse.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Scrobble
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktScrobbleResponse
|
||||
{
|
||||
[DataMember(Name = "action")]
|
||||
public string Action { get; set; }
|
||||
|
||||
[DataMember(Name = "progress")]
|
||||
public float Progress { get; set; }
|
||||
|
||||
[DataMember(Name = "sharing")]
|
||||
public SocialMedia Sharing { get; set; }
|
||||
|
||||
[DataContract]
|
||||
public class SocialMedia
|
||||
{
|
||||
[DataMember(Name = "facebook")]
|
||||
public bool Facebook { get; set; }
|
||||
|
||||
[DataMember(Name = "twitter")]
|
||||
public bool Twitter { get; set; }
|
||||
|
||||
[DataMember(Name = "tumblr")]
|
||||
public bool Tumblr { get; set; }
|
||||
}
|
||||
|
||||
[DataMember(Name = "movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
|
||||
[DataMember(Name = "episode")]
|
||||
public TraktEpisode Episode { get; set; }
|
||||
|
||||
[DataMember(Name = "show")]
|
||||
public TraktShow Show { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktEpisodeCollected : TraktEpisode
|
||||
{
|
||||
[DataMember(Name = "collected_at", EmitDefaultValue = false)]
|
||||
public string CollectedAt { get; set; }
|
||||
|
||||
[DataMember(Name = "media_type", EmitDefaultValue = false)]
|
||||
public string MediaType { get; set; }
|
||||
|
||||
[DataMember(Name = "resolution", EmitDefaultValue = false)]
|
||||
public string Resolution { get; set; }
|
||||
|
||||
[DataMember(Name = "audio", EmitDefaultValue = false)]
|
||||
public string Audio { get; set; }
|
||||
|
||||
[DataMember(Name = "audio_channels", EmitDefaultValue = false)]
|
||||
public string AudioChannels { get; set; }
|
||||
|
||||
[DataMember(Name = "3d", EmitDefaultValue = false)]
|
||||
public bool Is3D { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktMovieCollected : TraktMovie
|
||||
{
|
||||
[DataMember(Name = "collected_at", EmitDefaultValue = false)]
|
||||
public string CollectedAt { get; set; }
|
||||
|
||||
[DataMember(Name = "media_type", EmitDefaultValue = false)]
|
||||
public string MediaType { get; set; }
|
||||
|
||||
[DataMember(Name = "resolution", EmitDefaultValue = false)]
|
||||
public string Resolution { get; set; }
|
||||
|
||||
[DataMember(Name = "audio", EmitDefaultValue = false)]
|
||||
public string Audio { get; set; }
|
||||
|
||||
[DataMember(Name = "audio_channels", EmitDefaultValue = false)]
|
||||
public string AudioChannels { get; set; }
|
||||
|
||||
[DataMember(Name = "3d", EmitDefaultValue = false)]
|
||||
public bool Is3D { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Collection
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktShowCollected : TraktShow
|
||||
{
|
||||
[DataMember(Name = "seasons")]
|
||||
public List<TraktSeasonCollected> Seasons { get; set; }
|
||||
|
||||
[DataContract]
|
||||
public class TraktSeasonCollected
|
||||
{
|
||||
[DataMember(Name = "number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[DataMember(Name = "episodes")]
|
||||
public List<TraktEpisodeCollected> Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
15
Trakt/Api/DataContracts/Sync/Ratings/TraktEpisodeRated.cs
Normal file
15
Trakt/Api/DataContracts/Sync/Ratings/TraktEpisodeRated.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktEpisodeRated : TraktRated
|
||||
{
|
||||
[DataMember(Name = "number", EmitDefaultValue = false)]
|
||||
public int? Number { get; set; }
|
||||
|
||||
[DataMember(Name = "ids")]
|
||||
public TraktEpisodeId Ids { get; set; }
|
||||
}
|
||||
}
|
18
Trakt/Api/DataContracts/Sync/Ratings/TraktMovieRated.cs
Normal file
18
Trakt/Api/DataContracts/Sync/Ratings/TraktMovieRated.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktMovieRated : TraktRated
|
||||
{
|
||||
[DataMember(Name = "title", EmitDefaultValue = false)]
|
||||
public string Title { get; set; }
|
||||
|
||||
[DataMember(Name = "year", EmitDefaultValue = false)]
|
||||
public int? Year { get; set; }
|
||||
|
||||
[DataMember(Name = "ids")]
|
||||
public TraktMovieId Ids { get; set; }
|
||||
}
|
||||
}
|
31
Trakt/Api/DataContracts/Sync/Ratings/TraktShowRated.cs
Normal file
31
Trakt/Api/DataContracts/Sync/Ratings/TraktShowRated.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Ratings
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktShowRated : TraktRated
|
||||
{
|
||||
[DataMember(Name = "title", EmitDefaultValue = false)]
|
||||
public string Title { get; set; }
|
||||
|
||||
[DataMember(Name = "year", EmitDefaultValue = false)]
|
||||
public int? Year { get; set; }
|
||||
|
||||
[DataMember(Name = "ids")]
|
||||
public TraktShowId Ids { get; set; }
|
||||
|
||||
[DataMember(Name = "seasons")]
|
||||
public List<TraktSeasonRated> Seasons { get; set; }
|
||||
|
||||
public class TraktSeasonRated : TraktRated
|
||||
{
|
||||
[DataMember(Name = "number")]
|
||||
public int? Number { get; set; }
|
||||
|
||||
[DataMember(Name = "episodes")]
|
||||
public List<TraktEpisodeRated> Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
36
Trakt/Api/DataContracts/Sync/TraktSync.cs
Normal file
36
Trakt/Api/DataContracts/Sync/TraktSync.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.Sync.Collection;
|
||||
using Trakt.Api.DataContracts.Sync.Ratings;
|
||||
using Trakt.Api.DataContracts.Sync.Watched;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktSync<TMovie, TShow, TEpisode>
|
||||
{
|
||||
[DataMember(Name = "movies", EmitDefaultValue = false)]
|
||||
public List<TMovie> Movies { get; set; }
|
||||
|
||||
[DataMember(Name = "shows", EmitDefaultValue = false)]
|
||||
public List<TShow> Shows { get; set; }
|
||||
|
||||
[DataMember(Name = "episodes", EmitDefaultValue = false)]
|
||||
public List<TEpisode> Episodes { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class TraktSyncRated : TraktSync<TraktMovieRated, TraktShowRated, TraktEpisodeRated>
|
||||
{
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class TraktSyncWatched : TraktSync<TraktMovieWatched, TraktShowWatched, TraktEpisodeWatched>
|
||||
{
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class TraktSyncCollected : TraktSync<TraktMovieCollected, TraktShowCollected, TraktEpisodeCollected>
|
||||
{
|
||||
}
|
||||
}
|
60
Trakt/Api/DataContracts/Sync/TraktSyncResponse.cs
Normal file
60
Trakt/Api/DataContracts/Sync/TraktSyncResponse.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktSyncResponse
|
||||
{
|
||||
[DataMember(Name = "added")]
|
||||
public Items Added { get; set; }
|
||||
|
||||
[DataMember(Name = "deleted")]
|
||||
public Items Deleted { get; set; }
|
||||
|
||||
[DataMember(Name = "existing")]
|
||||
public Items Existing { get; set; }
|
||||
|
||||
[DataContract]
|
||||
public class Items
|
||||
{
|
||||
[DataMember(Name = "movies")]
|
||||
public int Movies { get; set; }
|
||||
|
||||
[DataMember(Name = "shows")]
|
||||
public int Shows { get; set; }
|
||||
|
||||
[DataMember(Name = "seasons")]
|
||||
public int Seasons { get; set; }
|
||||
|
||||
[DataMember(Name = "episodes")]
|
||||
public int Episodes { get; set; }
|
||||
|
||||
[DataMember(Name = "people")]
|
||||
public int People { get; set; }
|
||||
}
|
||||
|
||||
[DataMember(Name = "not_found")]
|
||||
public NotFoundObjects NotFound { get; set; }
|
||||
|
||||
[DataContract]
|
||||
public class NotFoundObjects
|
||||
{
|
||||
[DataMember(Name = "movies")]
|
||||
public List<TraktMovie> Movies { get; set; }
|
||||
|
||||
[DataMember(Name = "shows")]
|
||||
public List<TraktShow> Shows { get; set; }
|
||||
|
||||
[DataMember(Name = "episodes")]
|
||||
public List<TraktEpisode> Episodes { get; set; }
|
||||
|
||||
[DataMember(Name = "seasons")]
|
||||
public List<TraktSeason> Seasons { get; set; }
|
||||
|
||||
[DataMember(Name = "people")]
|
||||
public List<TraktPerson> People { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
12
Trakt/Api/DataContracts/Sync/Watched/TraktEpisodeWatched.cs
Normal file
12
Trakt/Api/DataContracts/Sync/Watched/TraktEpisodeWatched.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktEpisodeWatched : TraktEpisode
|
||||
{
|
||||
[DataMember(Name = "watched_at", EmitDefaultValue = false)]
|
||||
public string WatchedAt { get; set; }
|
||||
}
|
||||
}
|
12
Trakt/Api/DataContracts/Sync/Watched/TraktMovieWatched.cs
Normal file
12
Trakt/Api/DataContracts/Sync/Watched/TraktMovieWatched.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktMovieWatched : TraktMovie
|
||||
{
|
||||
[DataMember(Name = "watched_at", EmitDefaultValue = false)]
|
||||
public string WatchedAt { get; set; }
|
||||
}
|
||||
}
|
16
Trakt/Api/DataContracts/Sync/Watched/TraktSeasonWatched.cs
Normal file
16
Trakt/Api/DataContracts/Sync/Watched/TraktSeasonWatched.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktSeasonWatched : TraktSeason
|
||||
{
|
||||
[DataMember(Name = "watched_at", EmitDefaultValue = false)]
|
||||
public string WatchedAt { get; set; }
|
||||
|
||||
[DataMember(Name = "episodes")]
|
||||
public List<TraktEpisodeWatched> Episodes { get; set; }
|
||||
}
|
||||
}
|
16
Trakt/Api/DataContracts/Sync/Watched/TraktShowWatched.cs
Normal file
16
Trakt/Api/DataContracts/Sync/Watched/TraktShowWatched.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Sync.Watched
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktShowWatched : TraktShow
|
||||
{
|
||||
[DataMember(Name = "watched_at", EmitDefaultValue = false)]
|
||||
public string WatchedAt { get; set; }
|
||||
|
||||
[DataMember(Name = "seasons")]
|
||||
public List<TraktSeasonWatched> Seasons { get; set; }
|
||||
}
|
||||
}
|
11
Trakt/Api/DataContracts/TraktUserToken.cs
Normal file
11
Trakt/Api/DataContracts/TraktUserToken.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktUserToken
|
||||
{
|
||||
[DataMember(Name = "token")]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
14
Trakt/Api/DataContracts/TraktUserTokenRequest.cs
Normal file
14
Trakt/Api/DataContracts/TraktUserTokenRequest.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktUserTokenRequest
|
||||
{
|
||||
[DataMember(Name = "login")]
|
||||
public string Login { get; set; }
|
||||
|
||||
[DataMember(Name = "password")]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
23
Trakt/Api/DataContracts/Users/Collection/TraktMetadata.cs
Normal file
23
Trakt/Api/DataContracts/Users/Collection/TraktMetadata.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Collection
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktMetadata
|
||||
{
|
||||
[DataMember(Name = "media_type")]
|
||||
public string MediaType { get; set; }
|
||||
|
||||
[DataMember(Name = "resolution")]
|
||||
public string Resolution { get; set; }
|
||||
|
||||
[DataMember(Name = "audio")]
|
||||
public string Audio { get; set; }
|
||||
|
||||
[DataMember(Name = "audio_channels")]
|
||||
public string AudioChannels { get; set; }
|
||||
|
||||
[DataMember(Name = "3d")]
|
||||
public bool Is3D { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Collection
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktMovieCollected
|
||||
{
|
||||
[DataMember(Name = "collected_at")]
|
||||
public string CollectedAt { get; set; }
|
||||
|
||||
[DataMember(Name = "metadata")]
|
||||
public TraktMetadata Metadata { get; set; }
|
||||
|
||||
[DataMember(Name = "movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Collection
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktShowCollected
|
||||
{
|
||||
[DataMember(Name = "last_collected_at")]
|
||||
public string LastCollectedAt { get; set; }
|
||||
|
||||
[DataMember(Name = "show")]
|
||||
public TraktShow Show { get; set; }
|
||||
|
||||
[DataMember(Name = "seasons")]
|
||||
public List<TraktSeasonCollected> Seasons { get; set; }
|
||||
|
||||
[DataContract]
|
||||
public class TraktSeasonCollected
|
||||
{
|
||||
[DataMember(Name = "number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[DataMember(Name = "episodes")]
|
||||
public List<TraktEpisodeCollected> Episodes { get; set; }
|
||||
|
||||
[DataContract]
|
||||
public class TraktEpisodeCollected
|
||||
{
|
||||
[DataMember(Name = "number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[DataMember(Name = "collected_at")]
|
||||
public string CollectedAt { get; set; }
|
||||
|
||||
[DataMember(Name = "metadata")]
|
||||
public TraktMetadata Metadata { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
Trakt/Api/DataContracts/Users/Ratings/TraktEpisodeRated.cs
Normal file
12
Trakt/Api/DataContracts/Users/Ratings/TraktEpisodeRated.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktEpisodeRated : TraktRated
|
||||
{
|
||||
[DataMember(Name = "episode")]
|
||||
public TraktEpisode Episode { get; set; }
|
||||
}
|
||||
}
|
12
Trakt/Api/DataContracts/Users/Ratings/TraktMovieRated.cs
Normal file
12
Trakt/Api/DataContracts/Users/Ratings/TraktMovieRated.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktMovieRated : TraktRated
|
||||
{
|
||||
[DataMember(Name = "movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
}
|
||||
}
|
12
Trakt/Api/DataContracts/Users/Ratings/TraktSeasonRated.cs
Normal file
12
Trakt/Api/DataContracts/Users/Ratings/TraktSeasonRated.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktSeasonRated : TraktRated
|
||||
{
|
||||
[DataMember(Name = "season")]
|
||||
public TraktSeason Season { get; set; }
|
||||
}
|
||||
}
|
12
Trakt/Api/DataContracts/Users/Ratings/TraktShowRated.cs
Normal file
12
Trakt/Api/DataContracts/Users/Ratings/TraktShowRated.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Ratings
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktShowRated : TraktRated
|
||||
{
|
||||
[DataMember(Name = "show")]
|
||||
public TraktShow Show { get; set; }
|
||||
}
|
||||
}
|
18
Trakt/Api/DataContracts/Users/Watched/TraktMovieWatched.cs
Normal file
18
Trakt/Api/DataContracts/Users/Watched/TraktMovieWatched.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Watched
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktMovieWatched
|
||||
{
|
||||
[DataMember(Name = "plays")]
|
||||
public int Plays { get; set; }
|
||||
|
||||
[DataMember(Name = "last_watched_at")]
|
||||
public string LastWatchedAt { get; set; }
|
||||
|
||||
[DataMember(Name = "movie")]
|
||||
public TraktMovie Movie { get; set; }
|
||||
}
|
||||
}
|
42
Trakt/Api/DataContracts/Users/Watched/TraktShowWatched.cs
Normal file
42
Trakt/Api/DataContracts/Users/Watched/TraktShowWatched.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
|
||||
namespace Trakt.Api.DataContracts.Users.Watched
|
||||
{
|
||||
[DataContract]
|
||||
public class TraktShowWatched
|
||||
{
|
||||
[DataMember(Name = "plays")]
|
||||
public int Plays { get; set; }
|
||||
|
||||
[DataMember(Name = "last_watched_at")]
|
||||
public string WatchedAt { get; set; }
|
||||
|
||||
[DataMember(Name = "show")]
|
||||
public TraktShow Show { get; set; }
|
||||
|
||||
[DataMember(Name = "seasons")]
|
||||
public List<Season> Seasons { get; set; }
|
||||
|
||||
[DataContract]
|
||||
public class Season
|
||||
{
|
||||
[DataMember(Name = "number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[DataMember(Name = "episodes")]
|
||||
public List<Episode> Episodes { get; set; }
|
||||
|
||||
[DataContract]
|
||||
public class Episode
|
||||
{
|
||||
[DataMember(Name = "number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[DataMember(Name = "plays")]
|
||||
public int Plays { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
197
Trakt/Api/ServerApiEndpoints.cs
Normal file
197
Trakt/Api/ServerApiEndpoints.cs
Normal file
@ -0,0 +1,197 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using ServiceStack;
|
||||
using Trakt.Helpers;
|
||||
|
||||
namespace Trakt.Api
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Route("/Trakt/Users/{UserId}/Items/{Id}/Rate", "POST")]
|
||||
[Api(Description = "Tell the Trakt server to send an item rating to trakt.tv")]
|
||||
public class RateItem
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string UserId { get; set; }
|
||||
|
||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[ApiMember(Name = "Rating", Description = "Rating between 1 - 10 (0 = unrate)", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||
public int Rating { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Route("/Trakt/Users/{UserId}/Items/{Id}/Comment", "POST")]
|
||||
[Api(Description = "Tell the Trakt server to send an item comment to trakt.tv")]
|
||||
public class CommentItem
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string UserId { get; set; }
|
||||
|
||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[ApiMember(Name = "Comment", Description = "Text for the comment", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string Comment { get; set; }
|
||||
|
||||
[ApiMember(Name = "Spoiler", Description = "Set to true to indicate the comment contains spoilers. Defaults to false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
|
||||
public bool Spoiler { get; set; }
|
||||
|
||||
[ApiMember(Name = "Review", Description = "Set to true to indicate the comment is a 200+ word review. Defaults to false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
|
||||
public bool Review { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Route("/Trakt/Users/{UserId}/RecommendedMovies", "POST")]
|
||||
[Api(Description = "Request a list of recommended Movies based on a users watch history")]
|
||||
public class RecommendedMovies
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string UserId { get; set; }
|
||||
|
||||
[ApiMember(Name = "Genre", Description = "Genre slug to filter by. (See http://trakt.tv/api-docs/genres-movies)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public int Genre { get; set; }
|
||||
|
||||
[ApiMember(Name = "StartYear", Description = "4-digit year to filter movies released this year or later", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||
public int StartYear { get; set; }
|
||||
|
||||
[ApiMember(Name = "EndYear", Description = "4-digit year to filter movies released this year or earlier", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||
public int EndYear { get; set; }
|
||||
|
||||
[ApiMember(Name = "HideCollected", Description = "Set true to hide movies in the users collection", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
|
||||
public bool HideCollected { get; set; }
|
||||
|
||||
[ApiMember(Name = "HideWatchlisted", Description = "Set true to hide movies in the users watchlist", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
|
||||
public bool HideWatchlisted { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Route("/Trakt/Users/{UserId}/RecommendedShows", "POST")]
|
||||
[Api(Description = "Request a list of recommended Shows based on a users watch history")]
|
||||
public class RecommendedShows
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string UserId { get; set; }
|
||||
|
||||
[ApiMember(Name = "Genre", Description = "Genre slug to filter by. (See http://trakt.tv/api-docs/genres-shows)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public int Genre { get; set; }
|
||||
|
||||
[ApiMember(Name = "StartYear", Description = "4-digit year to filter shows released this year or later", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||
public int StartYear { get; set; }
|
||||
|
||||
[ApiMember(Name = "EndYear", Description = "4-digit year to filter shows released this year or earlier", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||
public int EndYear { get; set; }
|
||||
|
||||
[ApiMember(Name = "HideCollected", Description = "Set true to hide shows in the users collection", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
|
||||
public bool HideCollected { get; set; }
|
||||
|
||||
[ApiMember(Name = "HideWatchlisted", Description = "Set true to hide shows in the users watchlist", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
|
||||
public bool HideWatchlisted { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class TraktUriService : IRestfulService
|
||||
{
|
||||
private readonly TraktApi _traktApi;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TraktUriService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="traktApi">The trakt API.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
public TraktUriService(TraktApi traktApi, ILogger logger, ILibraryManager libraryManager)
|
||||
{
|
||||
_traktApi = traktApi;
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public object Post(RateItem request)
|
||||
{
|
||||
_logger.Info("RateItem request received");
|
||||
|
||||
var currentItem = _libraryManager.GetItemById(new Guid(request.Id));
|
||||
|
||||
if (currentItem == null)
|
||||
{
|
||||
_logger.Info("currentItem is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
return _traktApi.SendItemRating(currentItem, request.Rating, UserHelper.GetTraktUser(request.UserId)).Result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public object Post(CommentItem request)
|
||||
{
|
||||
_logger.Info("CommentItem request received");
|
||||
|
||||
var currentItem = _libraryManager.GetItemById(request.Id);
|
||||
|
||||
return _traktApi.SendItemComment(currentItem, request.Comment, request.Spoiler,
|
||||
UserHelper.GetTraktUser(request.UserId), request.Review).Result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public object Post(RecommendedMovies request)
|
||||
{
|
||||
return _traktApi.SendMovieRecommendationsRequest(UserHelper.GetTraktUser(request.UserId)).Result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public object Post(RecommendedShows request)
|
||||
{
|
||||
return _traktApi.SendShowRecommendationsRequest(UserHelper.GetTraktUser(request.UserId)).Result;
|
||||
}
|
||||
}
|
||||
}
|
1021
Trakt/Api/TraktApi.cs
Normal file
1021
Trakt/Api/TraktApi.cs
Normal file
File diff suppressed because it is too large
Load Diff
43
Trakt/Api/TraktURIs.cs
Normal file
43
Trakt/Api/TraktURIs.cs
Normal file
@ -0,0 +1,43 @@
|
||||
namespace Trakt.Api
|
||||
{
|
||||
public static class TraktUris
|
||||
{
|
||||
public const string Devkey = "0fabacd4bcf52604be7463374d2b9ee91995896ee410bb5ef9ce07ecc18db85c";
|
||||
|
||||
#region POST URI's
|
||||
public const string Login = @"https://api-v2launch.trakt.tv/auth/login";
|
||||
|
||||
public const string SyncCollectionAdd = @"https://api-v2launch.trakt.tv/sync/collection";
|
||||
public const string SyncCollectionRemove = @"https://api-v2launch.trakt.tv/sync/collection/remove";
|
||||
public const string SyncWatchedHistoryAdd = @"https://api-v2launch.trakt.tv/sync/history";
|
||||
public const string SyncWatchedHistoryRemove = @"https://api-v2launch.trakt.tv/sync/history/remove";
|
||||
public const string SyncRatingsAdd = @"https://api-v2launch.trakt.tv/sync/ratings";
|
||||
|
||||
public const string ScrobbleStart = @"https://api-v2launch.trakt.tv/scrobble/start";
|
||||
public const string ScrobblePause = @"https://api-v2launch.trakt.tv/scrobble/pause";
|
||||
public const string ScrobbleStop = @"https://api-v2launch.trakt.tv/scrobble/stop";
|
||||
#endregion
|
||||
|
||||
#region GET URI's
|
||||
|
||||
public const string WatchedMovies = @"https://api-v2launch.trakt.tv/sync/watched/movies";
|
||||
public const string WatchedShows = @"https://api-v2launch.trakt.tv/sync/watched/shows";
|
||||
public const string CollectedMovies = @"https://api-v2launch.trakt.tv/sync/collection/movies?extended=metadata";
|
||||
public const string CollectedShows = @"https://api-v2launch.trakt.tv/sync/collection/shows?extended=metadata";
|
||||
|
||||
// Recommendations
|
||||
public const string RecommendationsMovies = @"https://api-v2launch.trakt.tv/recommendations/movies";
|
||||
public const string RecommendationsShows = @"https://api-v2launch.trakt.tv/recommendations/shows";
|
||||
|
||||
#endregion
|
||||
|
||||
#region DELETE
|
||||
|
||||
// Recommendations
|
||||
public const string RecommendationsMoviesDismiss = @"https://api-v2launch.trakt.tv/recommendations/movies/{0}";
|
||||
public const string RecommendationsShowsDismiss = @"https://api-v2launch.trakt.tv/recommendations/shows/{0}";
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
15
Trakt/Configuration/PluginConfiguration.cs
Normal file
15
Trakt/Configuration/PluginConfiguration.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using MediaBrowser.Model.Plugins;
|
||||
using Trakt.Model;
|
||||
|
||||
namespace Trakt.Configuration
|
||||
{
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
public PluginConfiguration()
|
||||
{
|
||||
TraktUsers = new TraktUser[] {};
|
||||
}
|
||||
|
||||
public TraktUser[] TraktUsers { get; set; }
|
||||
}
|
||||
}
|
35
Trakt/Configuration/TraktConfigurationPage.cs
Normal file
35
Trakt/Configuration/TraktConfigurationPage.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using System.IO;
|
||||
|
||||
namespace Trakt.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Class TraktConfigurationPage
|
||||
/// </summary>
|
||||
class TraktConfigurationPage : IPluginConfigurationPage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name => "Trakt for MediaBrowser";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTML stream.
|
||||
/// </summary>
|
||||
/// <returns>Stream.</returns>
|
||||
public Stream GetHtmlStream()
|
||||
{
|
||||
return GetType().Assembly.GetManifestResourceStream(GetType().Namespace + ".configPage.html");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the configuration page.
|
||||
/// </summary>
|
||||
/// <value>The type of the configuration page.</value>
|
||||
public ConfigurationPageType ConfigurationPageType => ConfigurationPageType.PluginConfiguration;
|
||||
|
||||
public IPlugin Plugin => Trakt.Plugin.Instance;
|
||||
}
|
||||
}
|
214
Trakt/Configuration/configPage.html
Normal file
214
Trakt/Configuration/configPage.html
Normal file
@ -0,0 +1,214 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Trakt</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- ReSharper disable UnknownCssClass -->
|
||||
<div id="traktConfigurationPage" data-role="page" class="page type-interior pluginConfigurationPage">
|
||||
<div data-role="content">
|
||||
<div class="content-primary">
|
||||
|
||||
<form id="traktConfigurationForm">
|
||||
<ul class="ulForm" data-role="listview">
|
||||
<li>
|
||||
<label for="selectUser">Configure Trakt for:</label>
|
||||
<select id="selectUser" name="selectUser" onchange=" TraktConfigurationPage.loadConfiguration(this.value); "></select>
|
||||
</li>
|
||||
<li>
|
||||
<label for="txtTraktUserName">
|
||||
Trakt Username:
|
||||
</label>
|
||||
<input id="txtTraktUserName" name="txtTraktUserName" type="text" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="txtTraktPassword">
|
||||
Password:
|
||||
</label>
|
||||
<input id="txtTraktPassword" name="txtTraktPassword" type="password" />
|
||||
</li>
|
||||
<li>
|
||||
<div data-mini="true" data-role="collapsible">
|
||||
<h3>Locations Excluded:</h3>
|
||||
<div id="divTraktLocations">
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="chkSkipUnwatchedImportFromTrakt" name="chkSkipUnwatchedImportFromTrakt" />
|
||||
<label for="chkSkipUnwatchedImportFromTrakt">Skip unwatched import from trakt</label>
|
||||
<div class="fieldDescription">
|
||||
By default, Import from Trakt task will overwrite any watched status if item is marked
|
||||
as unwached on Trakt. When enabled only watched status will be imported.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="chkPostWatchedHistory" name="chkPostWatchedHistory" />
|
||||
<label for="chkPostWatchedHistory">Change watched history on trakt</label>
|
||||
<div class="fieldDescription">
|
||||
By default, watched status will be synchronized. This changes it so that emby just copies the status of trakt, and the only time data watched status is changed on trakt is when a video just finished playing (just scrobbles, no history changes).
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="chkExtraLogging" name="chkExtraLogging" />
|
||||
<label for="chkExtraLogging">Add Extra Logs</label>
|
||||
<div class="fieldDescription">
|
||||
When enabled, all data sent to trakt is logged.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="chkExportMediaInfo" name="chkExportMediaInfo" />
|
||||
<label for="chkExportMediaInfo">Export MediaInfo Metadata</label>
|
||||
<div class="fieldDescription">
|
||||
Send audio and video metadata to Trakt.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<button type="submit" data-theme="b">Save</button>
|
||||
<button type="button" onclick=" history.back(); ">Cancel</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ReSharper disable UseOfImplicitGlobalInFunctionScope -->
|
||||
<script type="text/javascript">
|
||||
|
||||
Array.prototype.remove = function () {
|
||||
var what, a = arguments, L = a.length, ax;
|
||||
while (L && this.length) {
|
||||
what = a[--L];
|
||||
while ((ax = this.indexOf(what)) !== -1) {
|
||||
this.splice(ax, 1);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
var TraktConfigurationPage =
|
||||
{
|
||||
pluginUniqueId: "8abc6789-fde2-4705-8592-4028806fa343",
|
||||
loadConfiguration: function (userId) {
|
||||
Dashboard.showLoadingMsg();
|
||||
ApiClient.getPluginConfiguration(TraktConfigurationPage.pluginUniqueId).then(function (config) {
|
||||
var currentUserConfig = config.TraktUsers.filter(function (curr) {
|
||||
return curr.LinkedMbUserId == userId;
|
||||
//return true;
|
||||
})[0];
|
||||
var page = $.mobile.activePage;
|
||||
// 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 = {
|
||||
UserName: "",
|
||||
Password: "",
|
||||
SkipUnwatchedImportFromTrakt: false,
|
||||
PostWatchedHistory: true,
|
||||
ExtraLogging: false,
|
||||
ExportMediaInfo: false
|
||||
};
|
||||
}
|
||||
// Default this to an empty array so the rendering code doesn't have to worry about it
|
||||
currentUserConfig.LocationsExcluded = currentUserConfig.LocationsExcluded || [];
|
||||
$('#txtTraktUserName', page).val(currentUserConfig.UserName);
|
||||
$('#txtTraktPassword', page).val(currentUserConfig.Password);
|
||||
$('#chkSkipUnwatchedImportFromTrakt', page).checked(currentUserConfig.SkipUnwatchedImportFromTrakt).checkboxradio("refresh");
|
||||
$('#chkPostWatchedHistory', page).checked(currentUserConfig.PostWatchedHistory).checkboxradio("refresh");
|
||||
$('#chkExtraLogging', page).checked(currentUserConfig.ExtraLogging).checkboxradio("refresh");
|
||||
$('#chkExportMediaInfo', page).checked(currentUserConfig.ExportMediaInfo).checkboxradio("refresh");
|
||||
// List the folders the user can access
|
||||
ApiClient.getVirtualFolders(userId).then(function (result) {
|
||||
TraktConfigurationPage.loadFolders(currentUserConfig, result);
|
||||
});
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
},
|
||||
populateUsers: function (users) {
|
||||
var html = "";
|
||||
for (var i = 0, length = users.length; i < length; i++) {
|
||||
var user = users[i];
|
||||
html += '<option value="' + user.Id + '">' + user.Name + '</option>';
|
||||
}
|
||||
$('#selectUser', $.mobile.activePage).html(html).selectmenu("refresh");
|
||||
},
|
||||
loadFolders: function (currentUserConfig, virtualFolders) {
|
||||
var page = $.mobile.activePage;
|
||||
var html = "";
|
||||
html += '<div data-role="controlgroup">';
|
||||
for (var i = 0, length = virtualFolders.length; i < length; i++) {
|
||||
var virtualFolder = virtualFolders[i];
|
||||
html += TraktConfigurationPage.getFolderHtml(currentUserConfig, virtualFolder, i);
|
||||
}
|
||||
html += '</div>';
|
||||
$('#divTraktLocations', page).html(html).trigger('create');
|
||||
},
|
||||
getFolderHtml: function (currentUserConfig, virtualFolder, index) {
|
||||
var html = "";
|
||||
for (var i = 0, length = virtualFolder.Locations.length; i < length; i++) {
|
||||
var id = "chkFolder" + index + "_" + i;
|
||||
var location = virtualFolder.Locations[i];
|
||||
var isChecked = currentUserConfig.LocationsExcluded.filter(function (current) {
|
||||
return current.toLowerCase() == location.toLowerCase();
|
||||
}).length;
|
||||
var checkedAttribute = isChecked ? 'checked="checked"' : "";
|
||||
html += '<label for="' + id + '">' + location + '</label>';
|
||||
html += '<input class="chkTraktLocation" type="checkbox" data-mini="true" id="' + id + '" name="' + id + '" data-location="' + location + '" ' + checkedAttribute + ' />';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
};
|
||||
$('#traktConfigurationPage').on('pageshow', function () {
|
||||
Dashboard.showLoadingMsg();
|
||||
var page = $.mobile.activePage;
|
||||
ApiClient.getUsers().then(function (users) {
|
||||
TraktConfigurationPage.populateUsers(users);
|
||||
var currentUserId = $('#selectUser', page).val();
|
||||
TraktConfigurationPage.loadConfiguration(currentUserId);
|
||||
});
|
||||
});
|
||||
$('#traktConfigurationForm').on('submit', function () {
|
||||
Dashboard.showLoadingMsg();
|
||||
var page = $.mobile.activePage;
|
||||
var currentUserId = $('#selectUser', page).val();
|
||||
ApiClient.getPluginConfiguration(TraktConfigurationPage.pluginUniqueId).then(function (config) {
|
||||
var currentUserConfig = config.TraktUsers.filter(function (curr) {
|
||||
return curr.LinkedMbUserId == currentUserId;
|
||||
})[0];
|
||||
// User doesn't have a config, so create a default one.
|
||||
if (!currentUserConfig) {
|
||||
currentUserConfig = {};
|
||||
config.TraktUsers.push(currentUserConfig);
|
||||
}
|
||||
currentUserConfig.SkipUnwatchedImportFromTrakt = $('#chkSkipUnwatchedImportFromTrakt', page).checked();
|
||||
currentUserConfig.PostWatchedHistory = $('#chkPostWatchedHistory', page).checked();
|
||||
currentUserConfig.ExtraLogging = $('#chkExtraLogging', page).checked();
|
||||
currentUserConfig.ExportMediaInfo = $('#chkExportMediaInfo', page).checked();
|
||||
currentUserConfig.UserName = $('#txtTraktUserName', page).val();
|
||||
if (currentUserConfig.Password != $('#txtTraktPassword', page).val()) {
|
||||
currentUserConfig.Password = $('#txtTraktPassword', page).val();
|
||||
}
|
||||
currentUserConfig.LinkedMbUserId = currentUserId;
|
||||
currentUserConfig.LocationsExcluded = $('.chkTraktLocation:checked', page).map(function () {
|
||||
return this.getAttribute('data-location');
|
||||
}).get();
|
||||
if (currentUserConfig.UserName == '') {
|
||||
config.TraktUsers.remove(config.TraktUsers.indexOf(currentUserConfig));
|
||||
}
|
||||
ApiClient.updatePluginConfiguration(TraktConfigurationPage.pluginUniqueId, config).then(function (result) {
|
||||
Dashboard.processPluginConfigurationUpdateResult(result);
|
||||
ApiClient.getUsers().then(function (users) {
|
||||
TraktConfigurationPage.populateUsers(users);
|
||||
Dashboard.alert('Settings saved.');
|
||||
});
|
||||
});
|
||||
});
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
<!-- ReSharper restore UseOfImplicitGlobalInFunctionScope -->
|
||||
|
||||
</div>
|
||||
<!-- ReSharper restore UnknownCssClass -->
|
||||
</body>
|
||||
</html>
|
9
Trakt/Enums.cs
Normal file
9
Trakt/Enums.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Trakt
|
||||
{
|
||||
public enum MediaStatus
|
||||
{
|
||||
Watching,
|
||||
Paused,
|
||||
Stop
|
||||
}
|
||||
}
|
208
Trakt/Extensions.cs
Normal file
208
Trakt/Extensions.cs
Normal file
@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Trakt.Api.DataContracts.Users.Collection;
|
||||
|
||||
namespace Trakt
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
// Trakt.tv uses Unix timestamps, which are seconds past epoch.
|
||||
public static DateTime ConvertEpochToDateTime(this long unixTimeStamp)
|
||||
{
|
||||
var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
|
||||
|
||||
dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
|
||||
|
||||
return dtDateTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static long ConvertToUnixTimeStamp(this DateTime dateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
|
||||
|
||||
var ts = dateTime.Subtract(dtDateTime);
|
||||
|
||||
return Convert.ToInt64(ts.TotalSeconds);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static int? ConvertToInt(this string input)
|
||||
{
|
||||
int result;
|
||||
if (int.TryParse(input, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsEmpty(this TraktMetadata metadata)
|
||||
{
|
||||
return 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.ToLower().Replace(" ", "_")
|
||||
: null;
|
||||
switch (audio)
|
||||
{
|
||||
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:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static string GetResolution(this MediaStream videoStream)
|
||||
{
|
||||
if (videoStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!videoStream.Width.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (videoStream.Width.Value >= 3800)
|
||||
{
|
||||
return "uhd_4k";
|
||||
}
|
||||
if (videoStream.Width.Value >= 1900)
|
||||
{
|
||||
return "hd_1080p";
|
||||
}
|
||||
if (videoStream.Width.Value >= 1270)
|
||||
{
|
||||
return "hd_720p";
|
||||
}
|
||||
if (videoStream.Width.Value >= 700)
|
||||
{
|
||||
return "sd_480p";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string ToISO8601(this DateTime dt, double hourShift = 0)
|
||||
{
|
||||
return dt.AddHours(hourShift).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ");
|
||||
}
|
||||
|
||||
|
||||
public static int GetSeasonNumber(this Episode episode)
|
||||
{
|
||||
return (episode.ParentIndexNumber != 0 ? episode.ParentIndexNumber ?? 1 + (episode.Series.AnimeSeriesIndex ?? 1) - 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 enum TraktAudio
|
||||
{
|
||||
lpcm,
|
||||
mp3,
|
||||
aac,
|
||||
dts,
|
||||
dts_ma,
|
||||
flac,
|
||||
ogg,
|
||||
wma,
|
||||
dolby_prologic,
|
||||
dolby_digital,
|
||||
dolby_digital_plus,
|
||||
dolby_truehd
|
||||
}
|
||||
}
|
||||
}
|
292
Trakt/Helpers/LibraryManagerEventsHelper.cs
Normal file
292
Trakt/Helpers/LibraryManagerEventsHelper.cs
Normal file
@ -0,0 +1,292 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using Trakt.Api;
|
||||
using Trakt.Model;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Trakt.Helpers
|
||||
{
|
||||
internal class LibraryManagerEventsHelper
|
||||
{
|
||||
private readonly List<LibraryEvent> _queuedEvents;
|
||||
private Timer _queueTimer;
|
||||
private readonly ILogger _logger ;
|
||||
private readonly TraktApi _traktApi;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="traktApi"></param>
|
||||
public LibraryManagerEventsHelper(ILogger logger, TraktApi traktApi)
|
||||
{
|
||||
_queuedEvents = new List<LibraryEvent>();
|
||||
_logger = logger;
|
||||
_traktApi = traktApi;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="eventType"></param>
|
||||
public void QueueItem(BaseItem item, EventType eventType)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException("item");
|
||||
|
||||
if (_queueTimer == null)
|
||||
{
|
||||
_queueTimer = new Timer(20000); // fire every 20 seconds
|
||||
_queueTimer.Elapsed += QueueTimerElapsed;
|
||||
}
|
||||
else if (_queueTimer.Enabled)
|
||||
{
|
||||
// If enabled then multiple LibraryManager events are firing. Restart the timer
|
||||
_queueTimer.Stop();
|
||||
_queueTimer.Start();
|
||||
}
|
||||
|
||||
if (!_queueTimer.Enabled)
|
||||
{
|
||||
_queueTimer.Enabled = true;
|
||||
}
|
||||
|
||||
|
||||
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>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void QueueTimerElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
_logger.Info("Timer elapsed - Processing queued items");
|
||||
|
||||
if (!_queuedEvents.Any())
|
||||
{
|
||||
_logger.Info("No events... Stopping queue timer");
|
||||
// This may need to go
|
||||
_queueTimer.Enabled = false;
|
||||
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.Info(queuedMovieDeletes.Count + " Movie Deletes to Process");
|
||||
ProcessQueuedMovieEvents(queuedMovieDeletes, traktUser, EventType.Remove);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("No Movie Deletes 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.Info(queuedMovieAdds.Count + " Movie Adds to Process");
|
||||
ProcessQueuedMovieEvents(queuedMovieAdds, traktUser, EventType.Add);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("No Movie Adds 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.Info(queuedEpisodeDeletes.Count + " Episode Deletes to Process");
|
||||
ProcessQueuedEpisodeEvents(queuedEpisodeDeletes, traktUser, EventType.Remove);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("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.Info(queuedEpisodeAdds.Count + " Episode Adds to Process");
|
||||
ProcessQueuedEpisodeEvents(queuedEpisodeAdds, traktUser, EventType.Add);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("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.Info(queuedMovieDeletes.Count + " Series Deletes to Process");
|
||||
ProcessQueuedShowEvents(queuedShowDeletes, traktUser, EventType.Remove);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("No Series Deletes to Process");
|
||||
}
|
||||
}
|
||||
|
||||
// Everything is processed. Reset the event list.
|
||||
_queueTimer.Enabled = false;
|
||||
_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(MetadataProviders.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, CancellationToken.None, eventType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Exception handled processing queued series events", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(MetadataProviders.Imdb)))
|
||||
.ToList();
|
||||
try
|
||||
{
|
||||
await _traktApi.SendLibraryUpdateAsync(movies, traktUser, CancellationToken.None, eventType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Exception handled processing queued movie events", ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <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(MetadataProviders.Tvdb))))
|
||||
.OrderBy(i => i.Series.Id)
|
||||
.ToList();
|
||||
|
||||
// Can't progress further without episodes
|
||||
if (!episodes.Any())
|
||||
{
|
||||
_logger.Info("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, CancellationToken.None, eventType);
|
||||
|
||||
currentSeriesId = ep.Series.Id;
|
||||
payload.Clear();
|
||||
}
|
||||
|
||||
payload.Add(ep);
|
||||
}
|
||||
|
||||
if (payload.Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
await _traktApi.SendLibraryUpdateAsync(payload, traktUser, CancellationToken.None, eventType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Exception handled processing queued episode events", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region internal helper types
|
||||
|
||||
internal class LibraryEvent
|
||||
{
|
||||
public BaseItem Item { get; set; }
|
||||
public TraktUser TraktUser { get; set; }
|
||||
public EventType EventType { get; set; }
|
||||
}
|
||||
|
||||
public enum EventType
|
||||
{
|
||||
Add,
|
||||
Remove,
|
||||
Update
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
206
Trakt/Helpers/UserDataManagerEventsHelper.cs
Normal file
206
Trakt/Helpers/UserDataManagerEventsHelper.cs
Normal file
@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Timers;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using Trakt.Api;
|
||||
using Trakt.Model;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
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
|
||||
{
|
||||
private List<UserDataPackage> _userDataPackages;
|
||||
private readonly ILogger _logger;
|
||||
private readonly TraktApi _traktApi;
|
||||
private Timer _timer;
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="traktApi"></param>
|
||||
public UserDataManagerEventsHelper(ILogger logger, TraktApi traktApi)
|
||||
{
|
||||
_userDataPackages = new List<UserDataPackage>();
|
||||
_logger = logger;
|
||||
_traktApi = traktApi;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <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)
|
||||
{
|
||||
userPackage = new UserDataPackage { TraktUser = traktUser };
|
||||
_userDataPackages.Add(userPackage);
|
||||
}
|
||||
|
||||
|
||||
if (_timer == null)
|
||||
{
|
||||
_timer = new Timer(5000);
|
||||
_timer.Elapsed += TimerElapsed;
|
||||
}
|
||||
|
||||
if (_timer.Enabled)
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
var movie = userDataSaveEventArgs.Item as Movie;
|
||||
|
||||
if (movie != null)
|
||||
{
|
||||
if (userDataSaveEventArgs.UserData.Played)
|
||||
{
|
||||
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>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
userPackage.UnSeenMovies.Add(movie);
|
||||
|
||||
if (userPackage.UnSeenMovies.Count >= 100)
|
||||
{
|
||||
_traktApi.SendMoviePlaystateUpdates(userPackage.UnSeenMovies, userPackage.TraktUser, false,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
userPackage.UnSeenMovies = new List<Movie>();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var episode = userDataSaveEventArgs.Item as Episode;
|
||||
|
||||
if (episode == null) 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 = new List<Episode>();
|
||||
}
|
||||
|
||||
if (userPackage.UnSeenEpisodes.Any())
|
||||
{
|
||||
_traktApi.SendEpisodePlaystateUpdates(userPackage.UnSeenEpisodes, userPackage.TraktUser, false,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
userPackage.SeenEpisodes = new List<Episode>();
|
||||
}
|
||||
|
||||
userPackage.CurrentSeriesId = episode.Series.Id;
|
||||
}
|
||||
|
||||
if (userDataSaveEventArgs.UserData.Played)
|
||||
{
|
||||
userPackage.SeenEpisodes.Add(episode);
|
||||
}
|
||||
else
|
||||
{
|
||||
userPackage.UnSeenEpisodes.Add(episode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
void TimerElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
_timer.Enabled = false;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Class that contains all the items to be reported to trakt.tv and supporting properties.
|
||||
/// </summary>
|
||||
internal class UserDataPackage
|
||||
{
|
||||
public TraktUser TraktUser;
|
||||
public Guid CurrentSeriesId;
|
||||
public List<Movie> SeenMovies;
|
||||
public List<Movie> UnSeenMovies;
|
||||
public List<Episode> SeenEpisodes;
|
||||
public List<Episode> UnSeenEpisodes;
|
||||
|
||||
public UserDataPackage()
|
||||
{
|
||||
SeenMovies = new List<Movie>();
|
||||
UnSeenMovies = new List<Movie>();
|
||||
SeenEpisodes = new List<Episode>();
|
||||
UnSeenEpisodes = new List<Episode>();
|
||||
}
|
||||
}
|
||||
}
|
21
Trakt/Helpers/UserHelpers.cs
Normal file
21
Trakt/Helpers/UserHelpers.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using Trakt.Model;
|
||||
|
||||
namespace Trakt.Helpers
|
||||
{
|
||||
internal static class UserHelper
|
||||
{
|
||||
public static TraktUser GetTraktUser(User user)
|
||||
{
|
||||
return Plugin.Instance.PluginConfiguration.TraktUsers != null ? Plugin.Instance.PluginConfiguration.TraktUsers.FirstOrDefault(tUser => new Guid(tUser.LinkedMbUserId).Equals(user.Id)) : null;
|
||||
}
|
||||
|
||||
public static TraktUser GetTraktUser(string userId)
|
||||
{
|
||||
var userGuid = new Guid(userId);
|
||||
return Plugin.Instance.PluginConfiguration.TraktUsers != null ? Plugin.Instance.PluginConfiguration.TraktUsers.FirstOrDefault(tUser => new Guid(tUser.LinkedMbUserId).Equals(userGuid)) : null;
|
||||
}
|
||||
}
|
||||
}
|
27
Trakt/Model/TraktUser.cs
Normal file
27
Trakt/Model/TraktUser.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace Trakt.Model
|
||||
{
|
||||
public class TraktUser
|
||||
{
|
||||
public String UserName { get; set; }
|
||||
|
||||
public String Password { get; set; }
|
||||
|
||||
public String LinkedMbUserId { get; set; }
|
||||
|
||||
public bool UsesAdvancedRating { get; set; }
|
||||
|
||||
public String UserToken { get; set; }
|
||||
|
||||
public bool SkipUnwatchedImportFromTrakt { get; set; }
|
||||
|
||||
public bool PostWatchedHistory { get; set; }
|
||||
|
||||
public bool ExtraLogging { get; set; }
|
||||
|
||||
public bool ExportMediaInfo { get; set; }
|
||||
|
||||
public String[] LocationsExcluded { get; set; }
|
||||
}
|
||||
}
|
29
Trakt/Plugin.cs
Normal file
29
Trakt/Plugin.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Trakt.Configuration;
|
||||
|
||||
namespace Trakt
|
||||
{
|
||||
public class Plugin : BasePlugin<PluginConfiguration>
|
||||
{
|
||||
public SemaphoreSlim TraktResourcePool = new SemaphoreSlim(2, 2);
|
||||
|
||||
public Plugin(IApplicationPaths appPaths, IXmlSerializer xmlSerializer)
|
||||
: base(appPaths, xmlSerializer)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public override string Name => "Trakt";
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
31
Trakt/Properties/AssemblyInfo.cs
Normal file
31
Trakt/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Trakt")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Trakt")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("8abc6789-fde2-4705-8592-4028806fa343")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
400
Trakt/ScheduledTasks/SyncFromTraktTask.cs
Normal file
400
Trakt/ScheduledTasks/SyncFromTraktTask.cs
Normal file
@ -0,0 +1,400 @@
|
||||
namespace Trakt.ScheduledTasks
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using CommonIO;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.ScheduledTasks;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
using Trakt.Api;
|
||||
using Trakt.Api.DataContracts.BaseModel;
|
||||
using Trakt.Api.DataContracts.Users.Collection;
|
||||
using Trakt.Api.DataContracts.Users.Watched;
|
||||
using Trakt.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Task that will Sync each users trakt.tv profile with their local library. This task will only include
|
||||
/// watched states.
|
||||
/// </summary>
|
||||
class SyncFromTraktTask : IScheduledTask
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly TraktApi _traktApi;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="jsonSerializer"></param>
|
||||
/// <param name="userManager"></param>
|
||||
/// <param name="userDataManager"> </param>
|
||||
/// <param name="httpClient"></param>
|
||||
/// <param name="appHost"></param>
|
||||
/// <param name="fileSystem"></param>
|
||||
public SyncFromTraktTask(ILogManager logger, IJsonSerializer jsonSerializer, IUserManager userManager, IUserDataManager userDataManager, IHttpClient httpClient, IServerApplicationHost appHost, IFileSystem fileSystem, ILibraryManager libraryManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_userDataManager = userDataManager;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger.GetLogger("Trakt");
|
||||
_traktApi = new TraktApi(jsonSerializer, _logger, httpClient, appHost, userDataManager, fileSystem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <param name="progress"></param>
|
||||
/// <returns></returns>
|
||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
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.Info("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, cancellationToken, progress, percentPerUser).ConfigureAwait(false);
|
||||
|
||||
numComplete++;
|
||||
currentProgress = percentPerUser * numComplete;
|
||||
progress.Report(currentProgress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error syncing trakt data for user {0}", ex, user.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SyncTraktDataForUser(User user, double currentProgress, CancellationToken cancellationToken, IProgress<double> progress, double percentPerUser)
|
||||
{
|
||||
var libraryRoot = user.RootFolder;
|
||||
var traktUser = UserHelper.GetTraktUser(user);
|
||||
|
||||
IEnumerable<TraktMovieWatched> traktWatchedMovies;
|
||||
IEnumerable<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.ErrorException("Exception handled", ex);
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.Info("Trakt.tv watched Movies count = " + traktWatchedMovies.Count());
|
||||
_logger.Info("Trakt.tv watched Shows count = " + traktWatchedShows.Count());
|
||||
|
||||
var mediaItems =
|
||||
_libraryManager.GetItemList(
|
||||
new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Episode).Name },
|
||||
ExcludeLocationTypes = new[] { LocationType.Virtual }
|
||||
})
|
||||
.Where(i => _traktApi.CanSync(i, traktUser))
|
||||
.OrderBy(
|
||||
i =>
|
||||
{
|
||||
var episode = i as Episode;
|
||||
|
||||
return episode != null ? episode.Series.Id : i.Id;
|
||||
}).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.Debug("Movie is in Watched list " + movie.Name);
|
||||
|
||||
var userData = _userDataManager.GetUserData(user.Id, movie);
|
||||
bool changed = false;
|
||||
|
||||
// set movie as watched
|
||||
if (!userData.Played)
|
||||
{
|
||||
userData.Played = true;
|
||||
userData.LastPlayedDate = DateTime.UtcNow;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// keep the highest play count
|
||||
int playcount = Math.Max(matchedMovie.Plays, userData.PlayCount);
|
||||
|
||||
// set movie playcount
|
||||
if (userData.PlayCount != playcount)
|
||||
{
|
||||
userData.PlayCount = playcount;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Set last played to whichever is most recent, remote or local time...
|
||||
if (!string.IsNullOrEmpty(matchedMovie.LastWatchedAt))
|
||||
{
|
||||
var tLastPlayed = DateTime.Parse(matchedMovie.LastWatchedAt);
|
||||
var latestPlayed = tLastPlayed > userData.LastPlayedDate ? tLastPlayed : userData.LastPlayedDate;
|
||||
if (userData.LastPlayedDate != latestPlayed)
|
||||
{
|
||||
userData.LastPlayedDate = latestPlayed;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Only process if there's a change
|
||||
if (changed)
|
||||
{
|
||||
await
|
||||
_userDataManager.SaveUserData(
|
||||
user.Id,
|
||||
movie,
|
||||
userData,
|
||||
UserDataSaveReason.Import,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("Failed to match " + movie.Name);
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
currentProgress += percentPerItem;
|
||||
progress.Report(currentProgress);
|
||||
}
|
||||
|
||||
foreach (var episode in mediaItems.OfType<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) + (episode.Series.AnimeSeriesIndex ?? 1) - 1)));
|
||||
|
||||
// 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);
|
||||
bool changed = false;
|
||||
|
||||
var matchedEpisode =
|
||||
matchedSeason.Episodes.FirstOrDefault(x => x.Number == (episode.IndexNumber ?? -1));
|
||||
|
||||
if (matchedEpisode != null)
|
||||
{
|
||||
_logger.Debug("Episode is in Watched list " + GetVerboseEpisodeData(episode));
|
||||
|
||||
// Set episode as watched
|
||||
if (!userData.Played)
|
||||
{
|
||||
userData.Played = true;
|
||||
userData.LastPlayedDate = DateTime.UtcNow;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// keep the highest play count
|
||||
int playcount = Math.Max(matchedEpisode.Plays, userData.PlayCount);
|
||||
|
||||
// set episode playcount
|
||||
if (userData.PlayCount != playcount)
|
||||
{
|
||||
userData.PlayCount = playcount;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else if (!traktUser.SkipUnwatchedImportFromTrakt)
|
||||
{
|
||||
userData.Played = false;
|
||||
userData.PlayCount = 0;
|
||||
userData.LastPlayedDate = null;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// only process if changed
|
||||
if (changed)
|
||||
{
|
||||
await
|
||||
_userDataManager.SaveUserData(
|
||||
user.Id,
|
||||
episode,
|
||||
userData,
|
||||
UserDataSaveReason.Import,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("No Season match in Watched shows list " + GetVerboseEpisodeData(episode));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("No Show match in Watched shows list " + GetVerboseEpisodeData(episode));
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
currentProgress += percentPerItem;
|
||||
progress.Report(currentProgress);
|
||||
}
|
||||
|
||||
// _logger.Info(syncItemFailures + " items not parsed");
|
||||
}
|
||||
|
||||
private string GetVerboseEpisodeData(Episode episode)
|
||||
{
|
||||
string episodeString = string.Empty;
|
||||
episodeString += "Episode: " + (episode.ParentIndexNumber != null ? episode.ParentIndexNumber.ToString() : "null");
|
||||
episodeString += "x" + (episode.IndexNumber != null ? episode.IndexNumber.ToString() : "null");
|
||||
episodeString += " '" + episode.Name + "' ";
|
||||
episodeString += "Series: '" + (episode.Series != null
|
||||
? !string.IsNullOrWhiteSpace(episode.Series.Name)
|
||||
? episode.Series.Name
|
||||
: "null property"
|
||||
: "null class");
|
||||
episodeString += "'";
|
||||
|
||||
return episodeString;
|
||||
}
|
||||
|
||||
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(MetadataProviders.Imdb);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(imdb) &&
|
||||
string.Equals(imdb, movie.Ids.Imdb, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var tmdb = item.GetProviderId(MetadataProviders.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(MetadataProviders.Tvdb);
|
||||
if (!string.IsNullOrWhiteSpace(tvdb) &&
|
||||
string.Equals(tvdb, show.Ids.Tvdb.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var imdb = item.GetProviderId(MetadataProviders.Imdb);
|
||||
if (!string.IsNullOrWhiteSpace(imdb) &&
|
||||
string.Equals(imdb, show.Ids.Imdb, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
|
||||
{
|
||||
return new List<ITaskTrigger>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Name => "Import playstates from Trakt.tv";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Description => "Sync Watched/Unwatched status from Trakt.tv for each MB3 user that has a configured Trakt account";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Category => "Trakt";
|
||||
}
|
||||
}
|
501
Trakt/ScheduledTasks/SyncLibraryTask.cs
Normal file
501
Trakt/ScheduledTasks/SyncLibraryTask.cs
Normal file
@ -0,0 +1,501 @@
|
||||
namespace Trakt.ScheduledTasks
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using CommonIO;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.ScheduledTasks;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
using Trakt.Api;
|
||||
using Trakt.Api.DataContracts.Sync;
|
||||
using Trakt.Helpers;
|
||||
using Trakt.Model;
|
||||
|
||||
/// <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
|
||||
{
|
||||
//private readonly IHttpClient _httpClient;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly TraktApi _traktApi;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public SyncLibraryTask(ILogManager logger, IJsonSerializer jsonSerializer, IUserManager userManager, IUserDataManager userDataManager, IHttpClient httpClient, IServerApplicationHost appHost, IFileSystem fileSystem, ILibraryManager libraryManager)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_userManager = userManager;
|
||||
_userDataManager = userDataManager;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger.GetLogger("Trakt");
|
||||
_traktApi = new TraktApi(jsonSerializer, _logger, httpClient, appHost, userDataManager, fileSystem);
|
||||
}
|
||||
|
||||
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
|
||||
{
|
||||
return new List<ITaskTrigger>();
|
||||
}
|
||||
|
||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
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.Info("No Users returned");
|
||||
return;
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
var progPercent = 0.0;
|
||||
var percentPerUser = 100 / users.Count;
|
||||
|
||||
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.Error("traktUser is either null or has no linked MB account");
|
||||
continue;
|
||||
}
|
||||
|
||||
await SyncUserLibrary(user, traktUser, progPercent, percentPerUser, progress, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
progPercent += percentPerUser;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SyncUserLibrary(
|
||||
User user,
|
||||
TraktUser traktUser,
|
||||
double progPercent,
|
||||
double percentPerUser,
|
||||
IProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// purely for progress reporting
|
||||
var mediaItemsCount = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Episode).Name },
|
||||
ExcludeLocationTypes = new[] { LocationType.Virtual }
|
||||
})
|
||||
.Count(i => _traktApi.CanSync(i, traktUser));
|
||||
|
||||
if (mediaItemsCount == 0)
|
||||
{
|
||||
_logger.Info("No media found for '" + user.Name + "'.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Info(mediaItemsCount + " Items found for '" + user.Name + "'.");
|
||||
|
||||
var percentPerItem = (float)percentPerUser / mediaItemsCount / 2.0;
|
||||
|
||||
await SyncMovies(user, traktUser, progress, progPercent, percentPerItem, cancellationToken);
|
||||
await SyncShows(user, traktUser, progress, progPercent, percentPerItem, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sync watched and collected status of <see cref="Movie"/>s with trakt.
|
||||
/// </summary>
|
||||
/// <param name="user">
|
||||
/// <see cref="User"/> to get <see cref="UserItemData"/> (e.g. watched status) from.
|
||||
/// </param>
|
||||
/// <param name="traktUser">
|
||||
/// The <see cref="TraktUser"/> to sync with.
|
||||
/// </param>
|
||||
/// <param name="progress">
|
||||
/// Progress reporter.
|
||||
/// </param>
|
||||
/// <param name="progPercent">
|
||||
/// Initial progress value.
|
||||
/// </param>
|
||||
/// <param name="percentPerItem">
|
||||
/// Progress percent per item.
|
||||
/// </param>
|
||||
/// <param name="cancellationToken">
|
||||
/// The cancellation token.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Awaitable <see cref="Task"/>.
|
||||
/// </returns>
|
||||
private async Task SyncMovies(
|
||||
User user,
|
||||
TraktUser traktUser,
|
||||
IProgress<double> progress,
|
||||
double progPercent,
|
||||
double percentPerItem,
|
||||
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
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Movie).Name },
|
||||
ExcludeLocationTypes = new[] { LocationType.Virtual }
|
||||
})
|
||||
.Where(x => _traktApi.CanSync(x, traktUser))
|
||||
.OrderBy(x => x.Name)
|
||||
.ToList();
|
||||
var collectedMovies = new List<Movie>();
|
||||
var playedMovies = new List<Movie>();
|
||||
var unplayedMovies = new List<Movie>();
|
||||
|
||||
foreach (var child in libraryMovies)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var libraryMovie = child as Movie;
|
||||
var userData = _userDataManager.GetUserData(user.Id, child);
|
||||
|
||||
// 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
|
||||
{
|
||||
userData.Played = false;
|
||||
await
|
||||
_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)
|
||||
{
|
||||
unplayedMovies.Add(libraryMovie);
|
||||
}
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
progPercent += percentPerItem;
|
||||
progress.Report(progPercent);
|
||||
}
|
||||
|
||||
// send movies to mark collected
|
||||
await SendMovieCollectionUpdates(true, traktUser, collectedMovies, progress, progPercent, percentPerItem, cancellationToken);
|
||||
|
||||
// send movies to mark watched
|
||||
await SendMoviePlaystateUpdates(true, traktUser, playedMovies, progress, progPercent, percentPerItem, cancellationToken);
|
||||
|
||||
// send movies to mark unwatched
|
||||
await SendMoviePlaystateUpdates(false, traktUser, unplayedMovies, progress, progPercent, percentPerItem, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task SendMovieCollectionUpdates(
|
||||
bool collected,
|
||||
TraktUser traktUser,
|
||||
List<Movie> movies,
|
||||
IProgress<double> progress,
|
||||
double progPercent,
|
||||
double percentPerItem,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Info("Movies to " + (collected ? "add to" : "remove from") + " Collection: " + movies.Count);
|
||||
if (movies.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataContracts =
|
||||
await
|
||||
_traktApi.SendLibraryUpdateAsync(movies, traktUser, cancellationToken, collected ? EventType.Add : EventType.Remove)
|
||||
.ConfigureAwait(false);
|
||||
if (dataContracts != null)
|
||||
{
|
||||
foreach (var traktSyncResponse in dataContracts)
|
||||
{
|
||||
LogTraktResponseDataContract(traktSyncResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ArgumentNullException argNullEx)
|
||||
{
|
||||
_logger.ErrorException("ArgumentNullException handled sending movies to trakt.tv", argNullEx);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.ErrorException("Exception handled sending movies to trakt.tv", e);
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
progPercent += percentPerItem * movies.Count;
|
||||
progress.Report(progPercent);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendMoviePlaystateUpdates(
|
||||
bool seen,
|
||||
TraktUser traktUser,
|
||||
List<Movie> playedMovies,
|
||||
IProgress<double> progress,
|
||||
double progPercent,
|
||||
double percentPerItem,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Info("Movies to set " + (seen ? string.Empty : "un") + "watched: " + playedMovies.Count);
|
||||
if (playedMovies.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataContracts =
|
||||
await _traktApi.SendMoviePlaystateUpdates(playedMovies, traktUser, seen, cancellationToken);
|
||||
if (dataContracts != null)
|
||||
{
|
||||
foreach (var traktSyncResponse in dataContracts)
|
||||
{
|
||||
LogTraktResponseDataContract(traktSyncResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.ErrorException("Error updating movie play states", e);
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
progPercent += percentPerItem * playedMovies.Count;
|
||||
progress.Report(progPercent);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SyncShows(
|
||||
User user,
|
||||
TraktUser traktUser,
|
||||
IProgress<double> progress,
|
||||
double progPercent,
|
||||
double percentPerItem,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var traktWatchedShows = await _traktApi.SendGetWatchedShowsRequest(traktUser).ConfigureAwait(false);
|
||||
var traktCollectedShows = await _traktApi.SendGetCollectedShowsRequest(traktUser).ConfigureAwait(false);
|
||||
var episodeItems =
|
||||
_libraryManager.GetItemList(
|
||||
new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
ExcludeLocationTypes = new[] { LocationType.Virtual }
|
||||
})
|
||||
.Where(x => _traktApi.CanSync(x, traktUser))
|
||||
.OrderBy(x => (x as Episode)?.SeriesName)
|
||||
.ToList();
|
||||
|
||||
var collectedEpisodes = new List<Episode>();
|
||||
var playedEpisodes = new List<Episode>();
|
||||
var unplayedEpisodes = new List<Episode>();
|
||||
|
||||
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
|
||||
{
|
||||
userData.Played = false;
|
||||
await
|
||||
_userDataManager.SaveUserData(
|
||||
user.Id,
|
||||
episode,
|
||||
userData,
|
||||
UserDataSaveReason.Import,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
else if (userData != null && !userData.Played && isPlayedTraktTv)
|
||||
{
|
||||
// If the show has not been played locally but is played on trakt.tv then add it to the unplayed list
|
||||
unplayedEpisodes.Add(episode);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
progPercent += percentPerItem;
|
||||
progress.Report(progPercent);
|
||||
}
|
||||
|
||||
await SendEpisodeCollectionUpdates(true, traktUser, collectedEpisodes, progress, progPercent, percentPerItem, cancellationToken);
|
||||
|
||||
await SendEpisodePlaystateUpdates(true, traktUser, playedEpisodes, progress, progPercent, percentPerItem, cancellationToken);
|
||||
|
||||
await SendEpisodePlaystateUpdates(false, traktUser, unplayedEpisodes, progress, progPercent, percentPerItem, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task SendEpisodePlaystateUpdates(
|
||||
bool seen,
|
||||
TraktUser traktUser,
|
||||
List<Episode> playedEpisodes,
|
||||
IProgress<double> progress,
|
||||
double progPercent,
|
||||
double percentPerItem,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Info("Episodes to set " + (seen ? string.Empty : "un") + "watched: " + playedEpisodes.Count);
|
||||
if (playedEpisodes.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataContracts =
|
||||
await _traktApi.SendEpisodePlaystateUpdates(playedEpisodes, traktUser, seen, cancellationToken);
|
||||
dataContracts?.ForEach(LogTraktResponseDataContract);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.ErrorException("Error updating episode play states", e);
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
progPercent += percentPerItem * playedEpisodes.Count;
|
||||
progress.Report(progPercent);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendEpisodeCollectionUpdates(
|
||||
bool collected,
|
||||
TraktUser traktUser,
|
||||
List<Episode> collectedEpisodes,
|
||||
IProgress<double> progress,
|
||||
double progPercent,
|
||||
double percentPerItem,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Info("Episodes to add to Collection: " + collectedEpisodes.Count);
|
||||
if (collectedEpisodes.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataContracts =
|
||||
await
|
||||
_traktApi.SendLibraryUpdateAsync(collectedEpisodes, traktUser, cancellationToken, collected ? EventType.Add : EventType.Remove)
|
||||
.ConfigureAwait(false);
|
||||
if (dataContracts != null)
|
||||
{
|
||||
foreach (var traktSyncResponse in dataContracts)
|
||||
{
|
||||
LogTraktResponseDataContract(traktSyncResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ArgumentNullException argNullEx)
|
||||
{
|
||||
_logger.ErrorException("ArgumentNullException handled sending episodes to trakt.tv", argNullEx);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.ErrorException("Exception handled sending episodes to trakt.tv", e);
|
||||
}
|
||||
|
||||
// purely for progress reporting
|
||||
progPercent += percentPerItem * collectedEpisodes.Count;
|
||||
progress.Report(progPercent);
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
private void LogTraktResponseDataContract(TraktSyncResponse dataContract)
|
||||
{
|
||||
_logger.Debug("TraktResponse Added Movies: " + dataContract.Added.Movies);
|
||||
_logger.Debug("TraktResponse Added Shows: " + dataContract.Added.Shows);
|
||||
_logger.Debug("TraktResponse Added Seasons: " + dataContract.Added.Seasons);
|
||||
_logger.Debug("TraktResponse Added Episodes: " + dataContract.Added.Episodes);
|
||||
foreach (var traktMovie in dataContract.NotFound.Movies)
|
||||
{
|
||||
_logger.Error("TraktResponse not Found:" + _jsonSerializer.SerializeToString(traktMovie));
|
||||
}
|
||||
|
||||
foreach (var traktShow in dataContract.NotFound.Shows)
|
||||
{
|
||||
_logger.Error("TraktResponse not Found:" + _jsonSerializer.SerializeToString(traktShow));
|
||||
}
|
||||
|
||||
foreach (var traktSeason in dataContract.NotFound.Seasons)
|
||||
{
|
||||
_logger.Error("TraktResponse not Found:" + _jsonSerializer.SerializeToString(traktSeason));
|
||||
}
|
||||
|
||||
foreach (var traktEpisode in dataContract.NotFound.Episodes)
|
||||
{
|
||||
_logger.Error("TraktResponse not Found:" + _jsonSerializer.SerializeToString(traktEpisode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
310
Trakt/ServerMediator.cs
Normal file
310
Trakt/ServerMediator.cs
Normal file
@ -0,0 +1,310 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using CommonIO;
|
||||
using Trakt.Api;
|
||||
using Trakt.Helpers;
|
||||
|
||||
namespace Trakt
|
||||
{
|
||||
/// <summary>
|
||||
/// All communication between the server and the plugins server instance should occur in this class.
|
||||
/// </summary>
|
||||
public class ServerMediator : IServerEntryPoint
|
||||
{
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
private TraktApi _traktApi;
|
||||
private TraktUriService _service;
|
||||
private LibraryManagerEventsHelper _libraryManagerEventsHelper;
|
||||
private readonly UserDataManagerEventsHelper _userDataManagerEventsHelper;
|
||||
|
||||
public static ServerMediator Instance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="jsonSerializer"></param>
|
||||
/// <param name="sessionManager"> </param>
|
||||
/// <param name="userDataManager"></param>
|
||||
/// <param name="libraryManager"> </param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="httpClient"></param>
|
||||
/// <param name="appHost"></param>
|
||||
/// <param name="fileSystem"></param>
|
||||
public ServerMediator(IJsonSerializer jsonSerializer, ISessionManager sessionManager, IUserDataManager userDataManager, ILibraryManager libraryManager, ILogManager logger, IHttpClient httpClient, IServerApplicationHost appHost, IFileSystem fileSystem)
|
||||
{
|
||||
Instance = this;
|
||||
_sessionManager = sessionManager;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger.GetLogger("Trakt");
|
||||
|
||||
_traktApi = new TraktApi(jsonSerializer, _logger, httpClient, appHost, userDataManager, fileSystem);
|
||||
_service = new TraktUriService(_traktApi, _logger, _libraryManager);
|
||||
_libraryManagerEventsHelper = new LibraryManagerEventsHelper(_logger, _traktApi);
|
||||
_userDataManagerEventsHelper = new UserDataManagerEventsHelper(_logger, _traktApi);
|
||||
|
||||
userDataManager.UserDataSaved += _userDataManager_UserDataSaved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e)
|
||||
{
|
||||
// ignore change events for any reason other than manually toggling played.
|
||||
if (e.SaveReason != UserDataSaveReason.TogglePlayed) return;
|
||||
|
||||
var baseItem = e.Item as BaseItem;
|
||||
|
||||
if (baseItem != null)
|
||||
{
|
||||
// determine if user has trakt credentials
|
||||
var traktUser = UserHelper.GetTraktUser(e.UserId.ToString());
|
||||
|
||||
// Can't progress
|
||||
if (traktUser == null || !_traktApi.CanSync(baseItem, traktUser))
|
||||
return;
|
||||
|
||||
// We have a user and the item is in a trakt monitored location.
|
||||
_userDataManagerEventsHelper.ProcessUserDataSaveEventArgs(e, traktUser);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void Run()
|
||||
{
|
||||
_sessionManager.PlaybackStart += KernelPlaybackStart;
|
||||
_sessionManager.PlaybackStopped += KernelPlaybackStopped;
|
||||
_libraryManager.ItemAdded += LibraryManagerItemAdded;
|
||||
_libraryManager.ItemRemoved += LibraryManagerItemRemoved;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
void LibraryManagerItemRemoved(object sender, ItemChangeEventArgs e)
|
||||
{
|
||||
if (!(e.Item is Movie) && !(e.Item is Episode) && !(e.Item is Series)) return;
|
||||
if (e.Item.LocationType == LocationType.Virtual) return;
|
||||
_libraryManagerEventsHelper.QueueItem(e.Item, EventType.Remove);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
void LibraryManagerItemAdded(object sender, ItemChangeEventArgs e)
|
||||
{
|
||||
// Don't do anything if it's not a supported media type
|
||||
if (!(e.Item is Movie) && !(e.Item is Episode) && !(e.Item is Series)) return;
|
||||
if (e.Item.LocationType == LocationType.Virtual) return;
|
||||
_libraryManagerEventsHelper.QueueItem(e.Item, EventType.Add);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Let Trakt.tv know the user has started to watch something
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void KernelPlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("Playback Started");
|
||||
|
||||
if (e.Users == null || !e.Users.Any() || e.Item == null)
|
||||
{
|
||||
_logger.Error("Event details incomplete. Cannot process current media");
|
||||
return;
|
||||
}
|
||||
|
||||
// Since MB3 is user profile friendly, I'm going to need to do a user lookup every time something starts
|
||||
var traktUser = UserHelper.GetTraktUser(e.Users.FirstOrDefault());
|
||||
|
||||
if (traktUser == null)
|
||||
{
|
||||
_logger.Info("Could not match user with any stored credentials");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_traktApi.CanSync(e.Item, traktUser))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug(traktUser.LinkedMbUserId + " appears to be monitoring " + e.Item.Path);
|
||||
|
||||
var video = e.Item as Video;
|
||||
var progressPercent = video.RunTimeTicks.HasValue && video.RunTimeTicks != 0 ?
|
||||
(float)(e.PlaybackPositionTicks??0) / video.RunTimeTicks.Value * 100.0f : 0.0f;
|
||||
|
||||
try
|
||||
{
|
||||
if (video is Movie)
|
||||
{
|
||||
_logger.Debug("Send movie status update");
|
||||
await
|
||||
_traktApi.SendMovieStatusUpdateAsync(video as Movie, MediaStatus.Watching, traktUser, progressPercent).
|
||||
ConfigureAwait(false);
|
||||
}
|
||||
else if (video is Episode)
|
||||
{
|
||||
_logger.Debug("Send episode status update");
|
||||
await
|
||||
_traktApi.SendEpisodeStatusUpdateAsync(video as Episode, MediaStatus.Watching, traktUser, progressPercent).
|
||||
ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Exception handled sending status update", ex);
|
||||
}
|
||||
|
||||
var playEvent = new ProgressEvent
|
||||
{
|
||||
UserId = e.Users.First().Id,
|
||||
ItemId = e.Item.Id,
|
||||
LastApiAccess = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error sending watching status update", ex, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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="e"></param>
|
||||
private async void KernelPlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||
{
|
||||
if (e.Users == null || !e.Users.Any() || e.Item == null)
|
||||
{
|
||||
_logger.Error("Event details incomplete. Cannot process current media");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Info("Playback Stopped");
|
||||
var traktUser = UserHelper.GetTraktUser(e.Users.FirstOrDefault());
|
||||
|
||||
if (traktUser == null)
|
||||
{
|
||||
_logger.Error("Could not match trakt user");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_traktApi.CanSync(e.Item, traktUser))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var video = e.Item as Video;
|
||||
|
||||
if (e.PlayedToCompletion)
|
||||
{
|
||||
_logger.Info("Item is played. Scrobble");
|
||||
|
||||
try
|
||||
{
|
||||
if (video is Movie)
|
||||
{
|
||||
await
|
||||
_traktApi.SendMovieStatusUpdateAsync(video as Movie, MediaStatus.Stop, traktUser, 100).
|
||||
ConfigureAwait(false);
|
||||
}
|
||||
else if (video is Episode)
|
||||
{
|
||||
await
|
||||
_traktApi.SendEpisodeStatusUpdateAsync(video as Episode, MediaStatus.Stop, traktUser, 100)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Exception handled sending status update", ex);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var progressPercent = video.RunTimeTicks.HasValue && video.RunTimeTicks != 0 ?
|
||||
(float)(e.PlaybackPositionTicks ?? 0) / video.RunTimeTicks.Value * 100.0f : 0.0f;
|
||||
_logger.Info("Item Not fully played. Tell trakt.tv we are no longer watching but don't scrobble");
|
||||
|
||||
if (video is Movie)
|
||||
{
|
||||
await _traktApi.SendMovieStatusUpdateAsync(video as Movie, MediaStatus.Stop, traktUser, progressPercent);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _traktApi.SendEpisodeStatusUpdateAsync(video as Episode, MediaStatus.Stop, traktUser, progressPercent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error sending scrobble", ex, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_sessionManager.PlaybackStart -= KernelPlaybackStart;
|
||||
_sessionManager.PlaybackStopped -= KernelPlaybackStopped;
|
||||
_libraryManager.ItemAdded -= LibraryManagerItemAdded;
|
||||
_libraryManager.ItemRemoved -= LibraryManagerItemRemoved;
|
||||
_service = null;
|
||||
_traktApi = null;
|
||||
_libraryManagerEventsHelper = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ProgressEvent
|
||||
{
|
||||
public Guid UserId;
|
||||
public Guid ItemId;
|
||||
public DateTime LastApiAccess;
|
||||
}
|
||||
}
|
157
Trakt/Trakt.csproj
Normal file
157
Trakt/Trakt.csproj
Normal file
@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{7FFC306B-2680-49C7-8BE0-6358B2A8A409}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Trakt</RootNamespace>
|
||||
<AssemblyName>Trakt</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CommonIO, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CommonIO.1.0.0.9\lib\net45\CommonIO.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Interfaces.IO">
|
||||
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="MediaBrowser.Common, Version=3.1.6127.24820, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MediaBrowser.Common.3.0.662\lib\net45\MediaBrowser.Common.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="MediaBrowser.Controller, Version=3.1.6127.24818, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MediaBrowser.Server.Core.3.0.662\lib\net45\MediaBrowser.Controller.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="MediaBrowser.Model, Version=3.1.6127.24820, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MediaBrowser.Common.3.0.662\lib\net45\MediaBrowser.Model.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Patterns.Logging">
|
||||
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ServiceStack.Interfaces, Version=4.0.0.0, Culture=neutral, PublicKeyToken=e06fbc6124f57c43, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\ServiceStack.Interfaces.4.0.35\lib\portable-wp80+sl5+net40+win8+monotouch+monoandroid\ServiceStack.Interfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs">
|
||||
<Link>Properties\SharedVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktUserSummary.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktEpisode.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktEpisodeId.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktMovieId.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktShow.cs" />
|
||||
<Compile Include="Api\DataContracts\Comments\TraktComment.cs" />
|
||||
<Compile Include="Api\DataContracts\Scrobble\TraktScrobbleResponse.cs" />
|
||||
<Compile Include="Api\DataContracts\Scrobble\TraktScrobbleEpisode.cs" />
|
||||
<Compile Include="Api\DataContracts\Scrobble\TraktScrobbleMovie.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\Collection\TraktEpisodeCollected.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\Collection\TraktMovieCollected.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\Collection\TraktShowCollected.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\Ratings\TraktEpisodeRated.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\Ratings\TraktMovieRated.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\Ratings\TraktShowRated.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\TraktSync.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\Watched\TraktSeasonWatched.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\Watched\TraktEpisodeWatched.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\Watched\TraktMovieWatched.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\Watched\TraktShowWatched.cs" />
|
||||
<Compile Include="Api\DataContracts\TraktUserTokenRequest.cs" />
|
||||
<Compile Include="Api\DataContracts\Users\Collection\TraktShowCollected.cs" />
|
||||
<Compile Include="Api\DataContracts\Users\Ratings\TraktEpisodeRated.cs" />
|
||||
<Compile Include="Api\DataContracts\Users\Collection\TraktMetadata.cs" />
|
||||
<Compile Include="Api\DataContracts\Users\Ratings\TraktMovieRated.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktRated.cs" />
|
||||
<Compile Include="Api\DataContracts\Users\Ratings\TraktSeasonRated.cs" />
|
||||
<Compile Include="Api\DataContracts\Users\Ratings\TraktShowRated.cs" />
|
||||
<Compile Include="Api\DataContracts\Users\Collection\TraktMovieCollected.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktPerson.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktPersonId.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktSeason.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktSeasonId.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktId.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktMovie.cs" />
|
||||
<Compile Include="Api\DataContracts\BaseModel\TraktShowId.cs" />
|
||||
<Compile Include="Api\DataContracts\Sync\TraktSyncResponse.cs" />
|
||||
<Compile Include="Api\DataContracts\TraktUserToken.cs" />
|
||||
<Compile Include="Api\DataContracts\Users\Watched\TraktMovieWatched.cs" />
|
||||
<Compile Include="Api\DataContracts\Users\Watched\TraktShowWatched.cs" />
|
||||
<Compile Include="Api\ServerApiEndpoints.cs" />
|
||||
<Compile Include="Api\TraktApi.cs" />
|
||||
<Compile Include="Api\TraktURIs.cs" />
|
||||
<Compile Include="Configuration\PluginConfiguration.cs" />
|
||||
<Compile Include="Configuration\TraktConfigurationPage.cs" />
|
||||
<Compile Include="Enums.cs" />
|
||||
<Compile Include="Extensions.cs" />
|
||||
<Compile Include="Helpers\LibraryManagerEventsHelper.cs" />
|
||||
<Compile Include="Helpers\UserDataManagerEventsHelper.cs" />
|
||||
<Compile Include="Helpers\UserHelpers.cs" />
|
||||
<Compile Include="Model\TraktUser.cs" />
|
||||
<Compile Include="Plugin.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ScheduledTasks\SyncFromTraktTask.cs" />
|
||||
<Compile Include="ScheduledTasks\SyncLibraryTask.cs" />
|
||||
<Compile Include="ServerMediator.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Configuration\configPage.html" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\..\Emby.dev\ProgramData-Server\Plugins\" /y</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
9
Trakt/packages.config
Normal file
9
Trakt/packages.config
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
|
||||
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
|
||||
<package id="MediaBrowser.Common" version="3.0.662" targetFramework="net45" />
|
||||
<package id="MediaBrowser.Server.Core" version="3.0.662" targetFramework="net45" />
|
||||
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
||||
<package id="ServiceStack.Interfaces" version="4.0.35" targetFramework="net45" />
|
||||
</packages>
|
Loading…
Reference in New Issue
Block a user