mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-05-11 21:09:25 -05:00
added MetaNodes plugin
This commit is contained in:
@@ -4,3 +4,6 @@ Plugin.dll
|
||||
*/.vs
|
||||
*.suo
|
||||
TestStore/
|
||||
.vs/FileFlowsPlugins/v17/.futdcache.v1
|
||||
.vs/FileFlowsPlugins/DesignTimeBuild/.dtbcache.v2
|
||||
.vs/FileFlowsPlugins/project-colors.json
|
||||
|
||||
Binary file not shown.
+10
-38
@@ -26,7 +26,7 @@ namespace FileFlows.BasicNodes.File
|
||||
string dest = DestinationPath;
|
||||
if (string.IsNullOrEmpty(dest))
|
||||
{
|
||||
args.Logger.ELog("No destination specified");
|
||||
args.Logger?.ELog("No destination specified");
|
||||
args.Result = NodeResult.Failure;
|
||||
return -1;
|
||||
}
|
||||
@@ -47,52 +47,24 @@ namespace FileFlows.BasicNodes.File
|
||||
}
|
||||
|
||||
var destDir = fiDest.DirectoryName;
|
||||
if (Directory.Exists(destDir) == false)
|
||||
if (string.IsNullOrEmpty(destDir) == false && Directory.Exists(destDir) == false)
|
||||
Directory.CreateDirectory(destDir);
|
||||
|
||||
long fileSize = new FileInfo(args.WorkingFile).Length;
|
||||
string original = args.WorkingFile;
|
||||
if (args.MoveFile(dest) == false)
|
||||
return -1;
|
||||
|
||||
bool moved = false;
|
||||
Task task = Task.Run(() =>
|
||||
if (DeleteOriginal && original != args.FileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (System.IO.File.Exists(dest))
|
||||
System.IO.File.Delete(dest);
|
||||
args.Logger.ILog($"Moving file: \"{args.WorkingFile}\" to \"{dest}\"");
|
||||
System.IO.File.Move(args.WorkingFile, dest, true);
|
||||
|
||||
if (DeleteOriginal && args.WorkingFile != args.FileName)
|
||||
{
|
||||
System.IO.File.Delete(args.FileName);
|
||||
}
|
||||
args.SetWorkingFile(dest);
|
||||
|
||||
moved = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
System.IO.File.Delete(original);
|
||||
}catch(Exception ex)
|
||||
{
|
||||
args.Logger.ELog("Failed to move file: " + ex.Message);
|
||||
args.Logger?.WLog("Failed to delete original file: " + ex.Message);
|
||||
}
|
||||
});
|
||||
|
||||
while (task.IsCompleted == false)
|
||||
{
|
||||
long currentSize = 0;
|
||||
var destFileInfo = new FileInfo(dest);
|
||||
if (destFileInfo.Exists)
|
||||
currentSize = destFileInfo.Length;
|
||||
|
||||
args.PartPercentageUpdate(currentSize / fileSize * 100);
|
||||
System.Threading.Thread.Sleep(50);
|
||||
}
|
||||
|
||||
if (moved == false)
|
||||
return -1;
|
||||
|
||||
args.PartPercentageUpdate(100);
|
||||
|
||||
return base.Execute(args);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16
-7
@@ -1,20 +1,19 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicNodes", "BasicNodes\BasicNodes.csproj", "{7AE24315-9FE7-429F-83D9-C989CFF5420D}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicNodes", "BasicNodes\BasicNodes.csproj", "{7AE24315-9FE7-429F-83D9-C989CFF5420D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VideoNodes", "VideoNodes\VideoNodes.csproj", "{CF96D3D1-1D8B-47F7-BEA7-BB238F7A566A}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VideoNodes", "VideoNodes\VideoNodes.csproj", "{CF96D3D1-1D8B-47F7-BEA7-BB238F7A566A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetaNodes", "MetaNodes\MetaNodes.csproj", "{E6F8E9F6-31D7-4A89-966D-2690CA7C0528}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7AE24315-9FE7-429F-83D9-C989CFF5420D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7AE24315-9FE7-429F-83D9-C989CFF5420D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
@@ -24,5 +23,15 @@ Global
|
||||
{CF96D3D1-1D8B-47F7-BEA7-BB238F7A566A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CF96D3D1-1D8B-47F7-BEA7-BB238F7A566A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CF96D3D1-1D8B-47F7-BEA7-BB238F7A566A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E6F8E9F6-31D7-4A89-966D-2690CA7C0528}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E6F8E9F6-31D7-4A89-966D-2690CA7C0528}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E6F8E9F6-31D7-4A89-966D-2690CA7C0528}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E6F8E9F6-31D7-4A89-966D-2690CA7C0528}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B689155F-BBFF-477B-A041-35C1CAD6D24D}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MetaNodes
|
||||
{
|
||||
internal class Globals
|
||||
{
|
||||
public static string MOVIE_INFO = "MovieInfo";
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"Flow":{
|
||||
"Parts": {
|
||||
"MovieLookup": {
|
||||
"Description": "Looks performs a search on TheMovieDB.org.\nStores the Metadata inside the parameter 'MovieInfo'.\n\nOutputs 1: Movie found\nOutputs 2: Movie not found",
|
||||
"Fields": {
|
||||
"UseFolderName": "Use Folder Name",
|
||||
"UseFolderName-Help": "If the folder name should be used instead of the filename."
|
||||
}
|
||||
},
|
||||
"MovieRenamer": {
|
||||
"Description": "Renames the working file using the metadata stored in 'MovieInfo'.\nNote: MovieLookup should be executed in the flow before this node to work.\n\nOutput 1: File was renamed\nOutput 2: File failed to be renamed",
|
||||
"Fields": {
|
||||
"Pattern": "Pattern",
|
||||
"Pattern-Help": "The pattern to use to rename the folder. '{Title}', '{Year}', '{Extension}'.",
|
||||
"DestinationPath": "Destination Path",
|
||||
"DestinationPath-Help": "If the file should be moved to a different directory.",
|
||||
"LogOnly": "Log Only",
|
||||
"LogOnly-Help": "Turn on if you just want to test this node without it actually renaming the file"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace MetaNodes
|
||||
{
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class Plugin : FileFlows.Plugin.IPlugin
|
||||
{
|
||||
public string Name => "Meta Nodes";
|
||||
|
||||
public void Init() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace BasicNodes.Tests
|
||||
{
|
||||
using FileFlows.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal class TestLogger : ILogger
|
||||
{
|
||||
private List<string> Messages = new List<string>();
|
||||
|
||||
public void DLog(params object[] args) => Log("DBUG", args);
|
||||
|
||||
public void ELog(params object[] args) => Log("ERRR", args);
|
||||
|
||||
public void ILog(params object[] args) => Log("INFO", args);
|
||||
|
||||
public void WLog(params object[] args) => Log("WARN", args);
|
||||
|
||||
private void Log(string type, object[] args)
|
||||
{
|
||||
if (args == null || args.Length == 0)
|
||||
return;
|
||||
string message = type + " -> " +
|
||||
string.Join(", ", args.Select(x =>
|
||||
x == null ? "null" :
|
||||
x.GetType().IsPrimitive || x is string ? x.ToString() :
|
||||
System.Text.Json.JsonSerializer.Serialize(x)));
|
||||
Messages.Add(message);
|
||||
}
|
||||
|
||||
public bool Contains(string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
return false;
|
||||
|
||||
string log = string.Join(Environment.NewLine, Messages);
|
||||
return log.Contains(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace MetaNodes.Tests.TheMovieDb
|
||||
{
|
||||
using BasicNodes.Tests;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
using MetaNodes.TheMovieDb;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class MovieLookupTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void MovieLookupTests_File_Ghostbusters()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Ghostbusters.mkv");
|
||||
args.Logger = new TestLogger();
|
||||
|
||||
MovieLookup ml = new MovieLookup();
|
||||
ml.UseFolderName = false;
|
||||
|
||||
var result = ml.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
Assert.IsTrue(args.Parameters.ContainsKey(Globals.MOVIE_INFO));
|
||||
|
||||
var mi = args.Parameters[Globals.MOVIE_INFO] as MovieInfo;
|
||||
Assert.IsNotNull(mi);
|
||||
|
||||
Assert.AreEqual("Ghostbusters", mi.Title);
|
||||
Assert.AreEqual(1984, mi.ReleaseDate.Year);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MovieLookupTests_File_Ghostbusters2()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Ghostbusters 2.mkv");
|
||||
args.Logger = new TestLogger();
|
||||
|
||||
MovieLookup ml = new MovieLookup();
|
||||
ml.UseFolderName = false;
|
||||
|
||||
var result = ml.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
Assert.IsTrue(args.Parameters.ContainsKey(Globals.MOVIE_INFO));
|
||||
|
||||
var mi = args.Parameters[Globals.MOVIE_INFO] as MovieInfo;
|
||||
Assert.IsNotNull(mi);
|
||||
|
||||
Assert.AreEqual("Ghostbusters II", mi.Title);
|
||||
Assert.AreEqual(1989, mi.ReleaseDate.Year);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MovieLookupTests_File_WithDots()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Back.To.The.Future.2.mkv");
|
||||
args.Logger = new TestLogger();
|
||||
|
||||
MovieLookup ml = new MovieLookup();
|
||||
ml.UseFolderName = false;
|
||||
|
||||
var result = ml.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
Assert.IsTrue(args.Parameters.ContainsKey(Globals.MOVIE_INFO));
|
||||
|
||||
var mi = args.Parameters[Globals.MOVIE_INFO] as MovieInfo;
|
||||
Assert.IsNotNull(mi);
|
||||
|
||||
Assert.AreEqual("Back to the Future Part II", mi.Title);
|
||||
Assert.AreEqual(1989, mi.ReleaseDate.Year);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MovieLookupTests_File_WithYear()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Back.To.The.Future.1989.mkv");
|
||||
args.Logger = new TestLogger();
|
||||
|
||||
MovieLookup ml = new MovieLookup();
|
||||
ml.UseFolderName = false;
|
||||
|
||||
var result = ml.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
Assert.IsTrue(args.Parameters.ContainsKey(Globals.MOVIE_INFO));
|
||||
|
||||
var mi = args.Parameters[Globals.MOVIE_INFO] as MovieInfo;
|
||||
Assert.IsNotNull(mi);
|
||||
|
||||
Assert.AreEqual("Back to the Future Part II", mi.Title);
|
||||
Assert.AreEqual(1989, mi.ReleaseDate.Year);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MovieLookupTests_Folder_WithYear()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Back To The Future (1989)\Jaws.mkv");
|
||||
args.Logger = new TestLogger();
|
||||
|
||||
MovieLookup ml = new MovieLookup();
|
||||
ml.UseFolderName = true;
|
||||
|
||||
var result = ml.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
Assert.IsTrue(args.Parameters.ContainsKey(Globals.MOVIE_INFO));
|
||||
|
||||
var mi = args.Parameters[Globals.MOVIE_INFO] as MovieInfo;
|
||||
Assert.IsNotNull(mi);
|
||||
|
||||
Assert.AreEqual("Back to the Future Part II", mi.Title);
|
||||
Assert.AreEqual(1989, mi.ReleaseDate.Year);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,134 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace MetaNodes.Tests.TheMovieDb
|
||||
{
|
||||
using BasicNodes.Tests;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
using MetaNodes.TheMovieDb;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class RenamerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void RenamerTests_File_TitleYearExt()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Ghostbusters.mkv");
|
||||
var logger = new TestLogger();
|
||||
args.Logger = logger;
|
||||
args.SetParameter(Globals.MOVIE_INFO, new MovieInfo
|
||||
{
|
||||
Title = "Back to the Future Part II",
|
||||
ReleaseDate = new DateTime(1989, 5, 5)
|
||||
});
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = "{Title} ({Year}).{ext}";
|
||||
node.LogOnly = true;
|
||||
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
Assert.IsTrue(logger.Contains("Renaming file to: Back to the Future Part II (1989).mkv"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RenamerTests_File_TitleExt()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Ghostbusters.mkv");
|
||||
var logger = new TestLogger();
|
||||
args.Logger = logger;
|
||||
args.SetParameter(Globals.MOVIE_INFO, new MovieInfo
|
||||
{
|
||||
Title = "Back to the Future Part II",
|
||||
ReleaseDate = new DateTime(1989, 5, 5)
|
||||
});
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = "{Title}.{ext}";
|
||||
node.LogOnly = true;
|
||||
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
Assert.IsTrue(logger.Contains("Renaming file to: Back to the Future Part II.mkv"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RenamerTests_Folder_TitleYear_Windows()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Ghostbusters.mkv");
|
||||
var logger = new TestLogger();
|
||||
args.Logger = logger;
|
||||
args.SetParameter(Globals.MOVIE_INFO, new MovieInfo
|
||||
{
|
||||
Title = "Back to the Future Part II",
|
||||
ReleaseDate = new DateTime(1989, 5, 5)
|
||||
});
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = @"{Title} ({Year})\{Title}.{ext}";
|
||||
node.LogOnly = true;
|
||||
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
Assert.IsTrue(logger.Contains($"Renaming file to: Back to the Future Part II (1989){Path.DirectorySeparatorChar}Back to the Future Part II.mkv"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RenamerTests_Folder_TitleYear_Linux()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Ghostbusters.mkv");
|
||||
var logger = new TestLogger();
|
||||
args.Logger = logger;
|
||||
args.SetParameter(Globals.MOVIE_INFO, new MovieInfo
|
||||
{
|
||||
Title = "Back to the Future Part II",
|
||||
ReleaseDate = new DateTime(1989, 5, 5)
|
||||
});
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = @"{Title} ({Year})/{Title}.{ext}";
|
||||
node.LogOnly = true;
|
||||
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
Assert.IsTrue(logger.Contains($"Renaming file to: Back to the Future Part II (1989){Path.DirectorySeparatorChar}Back to the Future Part II.mkv"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RenamerTests_Folder_TitleYear_MoveActual()
|
||||
{
|
||||
string tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".mkv");
|
||||
string path = new FileInfo(tempFile).DirectoryName;
|
||||
File.WriteAllText(tempFile, "test");
|
||||
|
||||
var args = new FileFlows.Plugin.NodeParameters(tempFile);
|
||||
var logger = new TestLogger();
|
||||
args.Logger = logger;
|
||||
args.SetParameter(Globals.MOVIE_INFO, new MovieInfo
|
||||
{
|
||||
Title = "Back to the Future Part II",
|
||||
ReleaseDate = new DateTime(1989, 5, 5)
|
||||
});
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = @"{Title} ({Year})/{Title}.{ext}";
|
||||
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
string expectedShort = $"Back to the Future Part II (1989){Path.DirectorySeparatorChar}Back to the Future Part II.mkv";
|
||||
Assert.IsTrue(logger.Contains($"Renaming file to: " + expectedShort));
|
||||
|
||||
string expected = Path.Combine(path, expectedShort);
|
||||
Assert.IsTrue(File.Exists(expected));
|
||||
|
||||
Directory.Delete(new FileInfo(expected).DirectoryName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,105 @@
|
||||
namespace MetaNodes.TheMovieDb
|
||||
{
|
||||
using System.Text.RegularExpressions;
|
||||
using DM.MovieApi;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Attributes;
|
||||
|
||||
public class MovieLookup : Node
|
||||
{
|
||||
public override int Inputs => 1;
|
||||
public override int Outputs => 2;
|
||||
public override string Icon => "fas fa-film";
|
||||
|
||||
|
||||
[Boolean(1)]
|
||||
public bool UseFolderName { get; set; }
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
var fileInfo = new FileInfo(args.FileName);
|
||||
string lookupName = UseFolderName ? fileInfo.Directory.Name : fileInfo.Name.Substring(0, fileInfo.Name.LastIndexOf(fileInfo.Extension));
|
||||
lookupName = lookupName.Replace(".", " ").Replace("_", " ");
|
||||
|
||||
// look for year
|
||||
string year = string.Empty;
|
||||
var match = Regex.Match(lookupName, @"((19[2-9][0-9])|(20[0-9]{2}))(?=([\.\s_\-\)\]]|$))");
|
||||
if (match.Success)
|
||||
{
|
||||
year = match.Groups[1].Value;
|
||||
lookupName = lookupName.Replace(year, "");
|
||||
}
|
||||
|
||||
// remove double spaces incase they were added when removing the year
|
||||
while (lookupName.IndexOf(" ") > 0)
|
||||
lookupName = lookupName.Replace(" ", " ");
|
||||
|
||||
string bearerToken = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxZjVlNTAyNmJkMDM4YmZjZmU2MjI2MWU2ZGEwNjM0ZiIsInN1YiI6IjRiYzg4OTJjMDE3YTNjMGY5MjAwMDIyZCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.yMwyT8DEK1rF1gQMKJ-ZSy-dUGxFs5T345XwBLrvrWE";
|
||||
|
||||
// RegisterSettings only needs to be called one time when your application starts-up.
|
||||
MovieDbFactory.RegisterSettings(bearerToken);
|
||||
|
||||
var movieApi = MovieDbFactory.Create<IApiMovieRequest>().Value;
|
||||
|
||||
|
||||
ApiSearchResponse<MovieInfo> response = movieApi.SearchByTitleAsync(lookupName).Result;
|
||||
|
||||
// try find an exact match
|
||||
var result = response.Results.OrderBy(x =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(year) == false)
|
||||
{
|
||||
return year == x.ReleaseDate.Year.ToString() ? 0 : 1;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
.ThenBy(x => x.Title.ToLower().Trim().Replace(" ", "") == lookupName.ToLower().Trim().Replace(" ", "") ? 0 : 1)
|
||||
.ThenBy(x =>
|
||||
{
|
||||
// do some fuzzy logic with roman numerals
|
||||
var numMatch = Regex.Match(lookupName, @"[\s]([\d]+)$");
|
||||
if (numMatch.Success == false)
|
||||
return 0;
|
||||
int number = int.Parse(numMatch.Groups[1].Value);
|
||||
string roman = number switch
|
||||
{
|
||||
1 => "i",
|
||||
2 => "ii",
|
||||
3 => "iii",
|
||||
4 => "iv",
|
||||
5 => "v",
|
||||
6 => "vi,",
|
||||
7 => "vii",
|
||||
8 => "viii",
|
||||
9 => "ix",
|
||||
10 => "x",
|
||||
11 => "xi",
|
||||
12 => "xii",
|
||||
13 => "xiii",
|
||||
_ => string.Empty
|
||||
};
|
||||
string ln = lookupName.Substring(0, lookupName.LastIndexOf(number.ToString())).ToLower().Trim().Replace(" ", "");
|
||||
string softTitle = x.Title.ToLower().Replace(" ", "").Trim();
|
||||
if (softTitle == ln + roman)
|
||||
return 0;
|
||||
if (softTitle.StartsWith(ln) && softTitle.EndsWith(roman))
|
||||
return 0;
|
||||
return 1;
|
||||
})
|
||||
.ThenBy(x => lookupName.ToLower().Trim().Replace(" ", "").StartsWith(x.Title.ToLower().Trim().Replace(" ", "")) ? 0 : 1)
|
||||
.ThenBy(x => x.Title)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (result == null)
|
||||
return 2; // no match
|
||||
|
||||
args.SetParameter(Globals.MOVIE_INFO, result);
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
namespace MetaNodes.TheMovieDb
|
||||
{
|
||||
using System.Text.RegularExpressions;
|
||||
using DM.MovieApi;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Attributes;
|
||||
|
||||
public class MovieRenamer : Node
|
||||
{
|
||||
public override int Inputs => 1;
|
||||
public override int Outputs => 1;
|
||||
public override string Icon => "fas fa-font";
|
||||
|
||||
public string _Pattern = string.Empty;
|
||||
|
||||
[Text(1)]
|
||||
public string? Pattern
|
||||
{
|
||||
get => _Pattern;
|
||||
set { _Pattern = value ?? ""; }
|
||||
}
|
||||
|
||||
private string _DestinationPath = string.Empty;
|
||||
|
||||
[Folder(2)]
|
||||
public string DestinationPath
|
||||
{
|
||||
get => _DestinationPath;
|
||||
set { _DestinationPath = value ?? ""; }
|
||||
}
|
||||
|
||||
[Boolean(3)]
|
||||
public bool LogOnly { get; set; }
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
if(string.IsNullOrEmpty(Pattern))
|
||||
{
|
||||
args.Logger?.ELog("No pattern specified");
|
||||
return -1;
|
||||
}
|
||||
var movieInfo = args.GetParameter<MovieInfo>(Globals.MOVIE_INFO);
|
||||
if (movieInfo == null) {
|
||||
args.Logger?.ELog("MovieInfo not found, you must execute the Movie Lookup node first");
|
||||
return -1;
|
||||
}
|
||||
|
||||
string newFile = Pattern;
|
||||
// incase they set a linux path on windows or vice versa
|
||||
newFile = newFile.Replace('\\', Path.DirectorySeparatorChar);
|
||||
newFile = newFile.Replace('/', Path.DirectorySeparatorChar);
|
||||
|
||||
newFile = ReplaceVariable(newFile, "Year", movieInfo.ReleaseDate.Year.ToString());
|
||||
newFile = ReplaceVariable(newFile, "Title", movieInfo.Title);
|
||||
newFile = ReplaceVariable(newFile, "Extension", args.WorkingFile.Substring(args.WorkingFile.LastIndexOf(".")+1));
|
||||
newFile = ReplaceVariable(newFile, "Ext", args.WorkingFile.Substring(args.WorkingFile.LastIndexOf(".") + 1));
|
||||
|
||||
string destFolder = DestinationPath;
|
||||
if (string.IsNullOrEmpty(destFolder))
|
||||
destFolder = new FileInfo(args.WorkingFile).Directory?.FullName ?? "";
|
||||
|
||||
var dest = new FileInfo(Path.Combine(destFolder, newFile));
|
||||
|
||||
args.Logger?.ILog("Renaming file to: " + (string.IsNullOrEmpty(DestinationPath) ? "" : DestinationPath + Path.DirectorySeparatorChar) + newFile);
|
||||
|
||||
|
||||
if (LogOnly)
|
||||
return 1;
|
||||
|
||||
return args.MoveFile(dest.FullName) ? 1 : -1;
|
||||
}
|
||||
|
||||
private string ReplaceVariable(string input, string variable, string value)
|
||||
{
|
||||
return Regex.Replace(input, @"{" + Regex.Escape(variable) + @"}", value, RegexOptions.IgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace DM.MovieApi.ApiRequest
|
||||
{
|
||||
internal abstract class ApiRequestBase
|
||||
{
|
||||
private readonly IApiSettings _settings;
|
||||
|
||||
protected ApiRequestBase( IApiSettings settings )
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<T>> QueryAsync<T>( string command )
|
||||
=> await QueryAsync<T>( command, new Dictionary<string, string>() );
|
||||
|
||||
public async Task<ApiQueryResponse<T>> QueryAsync<T>( string command, IDictionary<string, string> parameters )
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
DateFormatHandling = DateFormatHandling.IsoDateFormat,
|
||||
};
|
||||
settings.Converters.Add( new IsoDateTimeConverterEx() );
|
||||
|
||||
Func<string, T> deserializer = json => JsonConvert.DeserializeObject<T>( json, settings );
|
||||
|
||||
return await QueryAsync( command, parameters, deserializer );
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<T>> QueryAsync<T>( string command, Func<string, T> deserializer )
|
||||
=> await QueryAsync( command, new Dictionary<string, string>(), deserializer );
|
||||
|
||||
public async Task<ApiQueryResponse<T>> QueryAsync<T>( string command, IDictionary<string, string> parameters, Func<string, T> deserializer )
|
||||
{
|
||||
using( HttpClient client = CreateClient() )
|
||||
{
|
||||
string cmd = CreateCommand( command, parameters );
|
||||
|
||||
HttpResponseMessage response = await client.GetAsync( cmd ).ConfigureAwait( false );
|
||||
|
||||
string json = await response.Content.ReadAsStringAsync().ConfigureAwait( false );
|
||||
|
||||
if( !response.IsSuccessStatusCode )
|
||||
{
|
||||
// rate limit will not exist if there is an error.
|
||||
var error = new ApiQueryResponse<T>
|
||||
{
|
||||
Error = JsonConvert.DeserializeObject<ApiError>( json ),
|
||||
CommandText = response.RequestMessage.RequestUri.ToString(),
|
||||
Json = json,
|
||||
};
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
var result = new ApiQueryResponse<T>
|
||||
{
|
||||
CommandText = response.RequestMessage.RequestUri.ToString(),
|
||||
Json = json,
|
||||
};
|
||||
|
||||
T item = deserializer( json );
|
||||
result.Item = item;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<T>> SearchAsync<T>( string command )
|
||||
=> await SearchAsync<T>( command, 1 );
|
||||
|
||||
public async Task<ApiSearchResponse<T>> SearchAsync<T>( string command, int pageNumber )
|
||||
=> await SearchAsync<T>( command, pageNumber, new Dictionary<string, string>() );
|
||||
|
||||
public async Task<ApiSearchResponse<T>> SearchAsync<T>( string command, IDictionary<string, string> parameters )
|
||||
=> await SearchAsync<T>( command, 1, parameters );
|
||||
|
||||
public async Task<ApiSearchResponse<T>> SearchAsync<T>( string command, int pageNumber, IDictionary<string, string> parameters )
|
||||
{
|
||||
pageNumber = pageNumber < 1 ? 1 : pageNumber;
|
||||
pageNumber = pageNumber > 1000 ? 1000 : pageNumber;
|
||||
|
||||
if( !parameters.Keys.Contains( "page", StringComparer.OrdinalIgnoreCase ) )
|
||||
{
|
||||
parameters.Add( "page", pageNumber.ToString() );
|
||||
}
|
||||
|
||||
using( HttpClient client = CreateClient() )
|
||||
{
|
||||
string cmd = CreateCommand( command, parameters );
|
||||
|
||||
HttpResponseMessage response = await client.GetAsync( cmd ).ConfigureAwait( false );
|
||||
|
||||
string json = await response.Content.ReadAsStringAsync().ConfigureAwait( false );
|
||||
|
||||
// rate limit will not exist if there is an error.
|
||||
if( !response.IsSuccessStatusCode )
|
||||
{
|
||||
var error = new ApiSearchResponse<T>
|
||||
{
|
||||
// This will throw up if the error is page number = 0; the resultant json will be: {"errors":["page must be greater than 0"]}
|
||||
// in other words, the json will not include a status_code. Asked the api devs and this is a known issue they are working on.
|
||||
// What to do? Nothing really, the page guard at the top of the method will keep the page number > 0.
|
||||
Error = JsonConvert.DeserializeObject<ApiError>( json ),
|
||||
CommandText = response.RequestMessage.RequestUri.ToString(),
|
||||
Json = json,
|
||||
};
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ApiSearchResponse<T>>( json, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore } );
|
||||
|
||||
result.CommandText = response.RequestMessage.RequestUri.ToString();
|
||||
result.Json = json;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
protected HttpClient CreateClient()
|
||||
{
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = false,
|
||||
UseCookies = false,
|
||||
UseDefaultCredentials = true,
|
||||
AutomaticDecompression = DecompressionMethods.GZip,
|
||||
};
|
||||
|
||||
var client = new HttpClient( handler );
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue( "application/json" ) );
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Bearer", _settings.BearerToken );
|
||||
client.BaseAddress = new Uri( _settings.ApiUrl );
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
protected string CreateCommand( string rootCommand )
|
||||
=> CreateCommand( rootCommand, new Dictionary<string, string>() );
|
||||
|
||||
protected string CreateCommand( string rootCommand, IDictionary<string, string> parameters )
|
||||
{
|
||||
string tokens = parameters.Any()
|
||||
? string.Join( "&", parameters.Select( x => x.Key + "=" + x.Value ) )
|
||||
: string.Empty;
|
||||
|
||||
if( string.IsNullOrWhiteSpace( tokens ) == false )
|
||||
{
|
||||
rootCommand += $"?{tokens}";
|
||||
}
|
||||
|
||||
return rootCommand;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace DM.MovieApi.ApiRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to provide a constraint for all MovieDb Api Request interfaces/classes.
|
||||
/// </summary>
|
||||
public interface IApiRequest
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace DM.MovieApi.ApiRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends the native Newtonsoft IsoDateTimeConverter to allow deserializing partial dates.
|
||||
/// </summary>
|
||||
public class IsoDateTimeConverterEx : IsoDateTimeConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the JSON representation of the object.
|
||||
/// </summary>
|
||||
/// <param name="reader">The Newtonsoft.Json.JsonReader to read from.</param>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <param name="existingValue">The existing value of object being read.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
/// <returns>The object value.</returns>
|
||||
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
|
||||
{
|
||||
ConditionalTraceReaderValue( reader );
|
||||
|
||||
try
|
||||
{
|
||||
return base.ReadJson( reader, objectType, existingValue, serializer );
|
||||
}
|
||||
catch( Exception ex ) when( ex is FormatException
|
||||
|| ex is JsonSerializationException jse
|
||||
&& jse.Message.Contains( "System.DateTime" ) )
|
||||
{
|
||||
string val = reader.Value?.ToString();
|
||||
|
||||
if( val?.Length == 4 && int.TryParse( val, out int year ) )
|
||||
{
|
||||
return new DateTime( year, 1, 1 );
|
||||
}
|
||||
|
||||
return default( DateTime );
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "DEBUG" )]
|
||||
private void ConditionalTraceReaderValue( JsonReader reader )
|
||||
{
|
||||
string val = reader.Value?.ToString();
|
||||
if( string.IsNullOrWhiteSpace( val ) )
|
||||
{
|
||||
val = "<empty>";
|
||||
}
|
||||
|
||||
Debug.WriteLine( $"IsoDateTimeConverterEx.JsonReader.Value: {val}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.ApiResponse
|
||||
{
|
||||
[DataContract]
|
||||
public class ApiError
|
||||
{
|
||||
private int _statusCode;
|
||||
|
||||
[DataMember( Name = "status_code" )]
|
||||
public int StatusCode
|
||||
{
|
||||
get => _statusCode;
|
||||
private set
|
||||
{
|
||||
_statusCode = value;
|
||||
|
||||
TmdbStatusCode = Enum.IsDefined( typeof( TmdbStatusCode ), _statusCode )
|
||||
? ( TmdbStatusCode )_statusCode
|
||||
: TmdbStatusCode.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember( Name = "status_message" )]
|
||||
public string Message { get; private set; }
|
||||
|
||||
public TmdbStatusCode TmdbStatusCode { get; private set; }
|
||||
|
||||
public override string ToString()
|
||||
=> $"Status: {StatusCode}: {Message}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace DM.MovieApi.ApiResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard response from an API call returning a single specific result.
|
||||
/// Multiple item based based results (i.e., searches) are returned with an <see cref="ApiQueryResponse{T}"/>.
|
||||
/// </summary>
|
||||
public class ApiQueryResponse<T> : ApiResponseBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The item returned from the API call.
|
||||
/// </summary>
|
||||
public T Item { get; internal set; }
|
||||
|
||||
public override string ToString()
|
||||
=> Item.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace DM.MovieApi.ApiResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all API responses from themoviedb.org.
|
||||
/// </summary>
|
||||
public abstract class ApiResponseBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains specific error information if an error was encountered during the API call to themoviedb.org.
|
||||
/// </summary>
|
||||
public ApiError Error { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The API command text used for the API call to themoviedb.org.
|
||||
/// </summary>
|
||||
public string CommandText { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The JSON returned from themoviedb.org based on the <see cref="CommandText"/> query.
|
||||
/// </summary>
|
||||
public string Json { get; internal set; }
|
||||
|
||||
public override string ToString()
|
||||
=> CommandText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
||||
|
||||
namespace DM.MovieApi.ApiResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard response from an API call returning a more than one result, i.e., a Search Result.
|
||||
/// Single item based results are returned with an
|
||||
/// <see cref="DM.MovieApi.ApiResponse.ApiQueryResponse{T}"/>.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class ApiSearchResponse<T> : ApiResponseBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of results from the search.
|
||||
/// </summary>
|
||||
[DataMember( Name = "results" )]
|
||||
public IReadOnlyList<T> Results { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current page number of the search result.
|
||||
/// </summary>
|
||||
[DataMember( Name = "page" )]
|
||||
public int PageNumber { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of pages found from the search result.
|
||||
/// </summary>
|
||||
[DataMember( Name = "total_pages" )]
|
||||
public int TotalPages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of results from the search.
|
||||
/// </summary>
|
||||
[DataMember( Name = "total_results" )]
|
||||
public int TotalResults { get; private set; }
|
||||
|
||||
public override string ToString()
|
||||
=> $"Page {PageNumber} of {TotalPages} ({TotalResults} total results)";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace DM.MovieApi.ApiResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// themoviedb.org Status Codes as defined by: https://www.themoviedb.org/documentation/api/status-codes
|
||||
/// </summary>
|
||||
[SuppressMessage( "ReSharper", "UnusedMember.Global" )]
|
||||
public enum TmdbStatusCode
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 200: Success.
|
||||
/// </summary>
|
||||
//[Description( "200: Success." )]
|
||||
Success = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 501: Invalid service: this service does not exist.
|
||||
/// </summary>
|
||||
//[Description( "501: Invalid service: this service does not exist." )]
|
||||
InvalidService = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 401: Authentication failed: You do not have permissions to access the service.
|
||||
/// </summary>
|
||||
//[Description( "401: Authentication failed: You do not have permissions to access the service." )]
|
||||
InsufficientPermissions = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 405: Invalid format: This service doesn't exist in that format.
|
||||
/// </summary>
|
||||
//[Description( "405: Invalid format: This service doesn't exist in that format." )]
|
||||
InvalidFormat = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 422: Invalid parameters: Your request parameters are incorrect.
|
||||
/// </summary>
|
||||
//[Description( "422: Invalid parameters: Your request parameters are incorrect." )]
|
||||
InvalidParameters = 5,
|
||||
|
||||
/// <summary>
|
||||
/// 404: Invalid id: The pre-requisite id is invalid or not found.
|
||||
/// </summary>
|
||||
//[Description( "404: Invalid id: The pre-requisite id is invalid or not found." )]
|
||||
InvalidId = 6,
|
||||
|
||||
/// <summary>
|
||||
/// 401: Invalid API key: You must be granted a valid key.
|
||||
/// </summary>
|
||||
//[Description( "401: Invalid API key: You must be granted a valid key." )]
|
||||
InvalidApiKey = 7,
|
||||
|
||||
/// <summary>
|
||||
/// 403: Duplicate entry: The data you tried to submit already exists.
|
||||
/// </summary>
|
||||
//[Description( "403: Duplicate entry: The data you tried to submit already exists." )]
|
||||
DuplicateEntry = 8,
|
||||
|
||||
/// <summary>
|
||||
/// 503: Service offline: This service is temporarily offline, try again later.
|
||||
/// </summary>
|
||||
//[Description( "503: Service offline: This service is temporarily offline, try again later." )]
|
||||
ServiceOffline = 9,
|
||||
|
||||
/// <summary>
|
||||
/// 503: Service offline: This service is temporarily offline, try again later.
|
||||
/// </summary>
|
||||
//[Description( "401: Suspended API key: Access to your account has been suspended, contact TMDb." )]
|
||||
SuspendedApiKey = 10,
|
||||
|
||||
/// <summary>
|
||||
/// 503: Service offline: This service is temporarily offline, try again later.
|
||||
/// </summary>
|
||||
//[Description( "500: Internal error: Something went wrong, contact TMDb." )]
|
||||
InternalError = 11,
|
||||
|
||||
/// <summary>
|
||||
/// 201: The item/record was updated successfully.
|
||||
/// </summary>
|
||||
//[Description( "201: The item/record was updated successfully." )]
|
||||
SuccessfulUpdate = 12,
|
||||
|
||||
/// <summary>
|
||||
/// 200: The item/record was deleted successfully.
|
||||
/// </summary>
|
||||
//[Description( "200: The item/record was deleted successfully." )]
|
||||
SuccessfulDelete = 13,
|
||||
|
||||
/// <summary>
|
||||
/// 401: Authentication failed.
|
||||
/// </summary>
|
||||
//[Description( "401: Authentication failed." )]
|
||||
AuthenticationFailed = 14,
|
||||
|
||||
/// <summary>
|
||||
/// 500: Failed.
|
||||
/// </summary>
|
||||
//[Description( "500: Failed." )]
|
||||
Failed = 15,
|
||||
|
||||
/// <summary>
|
||||
/// 401: Device denied.
|
||||
/// </summary>
|
||||
//[Description( "401: Device denied." )]
|
||||
DeviceDenied = 16,
|
||||
|
||||
/// <summary>
|
||||
/// 401: Session denied.
|
||||
/// </summary>
|
||||
//[Description( "401: Session denied." )]
|
||||
SessionDenied = 17,
|
||||
|
||||
/// <summary>
|
||||
/// 400: Validation failed.
|
||||
/// </summary>
|
||||
//[Description( "400: Validation failed." )]
|
||||
ValidationFailed = 18,
|
||||
|
||||
/// <summary>
|
||||
/// 406: Invalid accept header.
|
||||
/// </summary>
|
||||
//[Description( "406: Invalid accept header." )]
|
||||
InvalidAcceptHeader = 19,
|
||||
|
||||
/// <summary>
|
||||
/// 422: Invalid date range: Should be a range no longer than 14 days.
|
||||
/// </summary>
|
||||
//[Description( "422: Invalid date range: Should be a range no longer than 14 days." )]
|
||||
InvalidDateRange = 20,
|
||||
|
||||
/// <summary>
|
||||
/// 200: Entry not found: The item you are trying to edit cannot be found.
|
||||
/// </summary>
|
||||
//[Description( "200: Entry not found: The item you are trying to edit cannot be found." )]
|
||||
EntryNotFound = 21,
|
||||
|
||||
/// <summary>
|
||||
/// 400: Invalid page: Pages start at 1 and max at 1000. They are expected to be an integer.
|
||||
/// </summary>
|
||||
//[Description( "400: Invalid page: Pages start at 1 and max at 1000. They are expected to be an integer." )]
|
||||
InvalidPage = 22,
|
||||
|
||||
/// <summary>
|
||||
/// 400: Invalid date: Format needs to be YYYY-MM-DD.
|
||||
/// </summary>
|
||||
//[Description( "400: Invalid date: Format needs to be YYYY-MM-DD." )]
|
||||
InvalidDate = 23,
|
||||
|
||||
/// <summary>
|
||||
/// 400: Invalid date: Format needs to be YYYY-MM-DD.
|
||||
/// </summary>
|
||||
//[Description( "504: Your request to the backend server timed out. Try again." )]
|
||||
ServerTimeout = 24,
|
||||
|
||||
/// <summary>
|
||||
/// 400: Invalid date: Format needs to be YYYY-MM-DD.
|
||||
/// </summary>
|
||||
//[Description( "429: Your request count (#) is over the allowed limit of (40)." )]
|
||||
RequestOverLimit = 25,
|
||||
|
||||
/// <summary>
|
||||
/// "400: You must provide a username and password.
|
||||
/// </summary>
|
||||
//[Description( "400: You must provide a username and password." )]
|
||||
AuthenticationRequired = 26,
|
||||
|
||||
/// <summary>
|
||||
/// 400: Too many append to response objects: The maximum number of remote calls is 20.
|
||||
/// </summary>
|
||||
//[Description( "400: Too many append to response objects: The maximum number of remote calls is 20." )]
|
||||
ResponseObjectOverflow = 27,
|
||||
|
||||
/// <summary>
|
||||
/// 400: Invalid timezone: Please consult the documentation for a valid timezone.
|
||||
/// </summary>
|
||||
//[Description( "400: Invalid timezone: Please consult the documentation for a valid timezone." )]
|
||||
InvalidTimezone = 28,
|
||||
|
||||
/// <summary>
|
||||
/// 400: Invalid timezone: Please consult the documentation for a valid timezone.
|
||||
/// </summary>
|
||||
//[Description( "400: You must confirm this action: Please provide a confirm=true parameter." )]
|
||||
ActionMustBeConfirmed = 29,
|
||||
|
||||
/// <summary>
|
||||
/// 401: Invalid username and/or password: You did not provide a valid login.
|
||||
/// </summary>
|
||||
//[Description( "401: Invalid username and/or password: You did not provide a valid login." )]
|
||||
InvalidAuthentication = 30,
|
||||
|
||||
/// <summary>
|
||||
/// 401: Account disabled: Your account is no longer active. Contact TMDb if this is an error.
|
||||
/// </summary>
|
||||
//[Description( "401: Account disabled: Your account is no longer active. Contact TMDb if this is an error." )]
|
||||
AccountDisabled = 31,
|
||||
|
||||
/// <summary>
|
||||
/// 401: Email not verified: Your email address has not been verified.
|
||||
/// </summary>
|
||||
//[Description( "401: Email not verified: Your email address has not been verified." )]
|
||||
EmailNotVerified = 32,
|
||||
|
||||
/// <summary>
|
||||
/// 401: Invalid request token: The request token is either expired or invalid.
|
||||
/// </summary>
|
||||
//[Description( "401: Invalid request token: The request token is either expired or invalid." )]
|
||||
InvalidRequestToken = 33,
|
||||
|
||||
/// <summary>
|
||||
/// 401: The resource you requested could not be found.
|
||||
/// </summary>
|
||||
//[Description( "401: The resource you requested could not be found." )]
|
||||
ResourceNotFound = 34,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DM.MovieApi
|
||||
{
|
||||
internal interface IApiSettings
|
||||
{
|
||||
string ApiUrl { get; }
|
||||
|
||||
string BearerToken { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using DM.MovieApi.MovieDb.Certifications;
|
||||
using DM.MovieApi.MovieDb.Companies;
|
||||
using DM.MovieApi.MovieDb.Configuration;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
using DM.MovieApi.MovieDb.IndustryProfessions;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
using DM.MovieApi.MovieDb.People;
|
||||
using DM.MovieApi.MovieDb.TV;
|
||||
|
||||
namespace DM.MovieApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Global interface exposing all API interfaces against themoviedb.org that are
|
||||
/// currently available in this release.
|
||||
/// </summary>
|
||||
public interface IMovieDbApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access for retrieving production company information.
|
||||
/// </summary>
|
||||
IApiCompanyRequest Companies { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access for retrieving themoviedb.org configuration information.
|
||||
/// </summary>
|
||||
IApiConfigurationRequest Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access for retrieving Movie and TV genres.
|
||||
/// </summary>
|
||||
IApiGenreRequest Genres { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access for retrieving information about Movie/TV industry specific professions.
|
||||
/// </summary>
|
||||
IApiProfessionRequest IndustryProfessions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access for retrieving information about Movies.
|
||||
/// </summary>
|
||||
IApiMovieRequest Movies { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access for retrieving movie rating information.
|
||||
/// </summary>
|
||||
IApiMovieRatingRequest MovieRatings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access for retrieving information about Television shows.
|
||||
/// </summary>
|
||||
IApiTVShowRequest Television { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access for retrieving information about People.
|
||||
/// </summary>
|
||||
IApiPeopleRequest People { get; }
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 kindler chase
|
||||
|
||||
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.
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.Shims;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Certifications
|
||||
{
|
||||
internal class ApiMovieRatingRequest : ApiRequestBase, IApiMovieRatingRequest
|
||||
{
|
||||
[ImportingConstructor]
|
||||
public ApiMovieRatingRequest( IApiSettings settings )
|
||||
: base( settings )
|
||||
{ }
|
||||
|
||||
public async Task<ApiQueryResponse<MovieRatings>> GetMovieRatingsAsync()
|
||||
{
|
||||
const string command = "certification/movie/list";
|
||||
|
||||
ApiQueryResponse<MovieRatings> response = await base.QueryAsync( command, RatingsDeserializer );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private MovieRatings RatingsDeserializer( string json )
|
||||
{
|
||||
var obj = JObject.Parse( json );
|
||||
|
||||
JToken certs = obj["certifications"];
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
var ratings = certs.ToObject<MovieRatings>();
|
||||
|
||||
Func<IEnumerable<Certification>, IReadOnlyList<Certification>> reorder =
|
||||
list => list.OrderBy( x => x.Order ).ThenBy( x => x.Rating ).ToList().AsReadOnly();
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
ratings.Australia = reorder( ratings.Australia );
|
||||
ratings.Canada = reorder( ratings.Canada );
|
||||
ratings.France = reorder( ratings.France );
|
||||
ratings.Germany = reorder( ratings.Germany );
|
||||
ratings.India = reorder( ratings.India );
|
||||
ratings.NewZealand = reorder( ratings.NewZealand );
|
||||
ratings.UnitedKingdom = reorder( ratings.UnitedKingdom );
|
||||
ratings.UnitedStates = reorder( ratings.UnitedStates );
|
||||
|
||||
return ratings;
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Certifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for retrieving movie rating information.
|
||||
/// </summary>
|
||||
public interface IApiMovieRatingRequest : IApiRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of supported certifications (movie ratings) for movies.
|
||||
/// </summary>
|
||||
Task<ApiQueryResponse<MovieRatings>> GetMovieRatingsAsync();
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Certifications
|
||||
{
|
||||
[DataContract]
|
||||
public class Certification
|
||||
{
|
||||
[DataMember( Name = "certification" )]
|
||||
public string Rating { get; set; }
|
||||
|
||||
[DataMember( Name = "meaning" )]
|
||||
public string Meaning { get; set; }
|
||||
|
||||
[DataMember( Name = "order" )]
|
||||
public int Order { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Rating}: {Meaning.Substring( 75 )}";
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class MovieRatings
|
||||
{
|
||||
[DataMember( Name = "AU" )]
|
||||
public IReadOnlyList<Certification> Australia { get; set; }
|
||||
|
||||
[DataMember( Name = "CA" )]
|
||||
public IReadOnlyList<Certification> Canada { get; set; }
|
||||
|
||||
[DataMember( Name = "FR" )]
|
||||
public IReadOnlyList<Certification> France { get; set; }
|
||||
|
||||
[DataMember( Name = "DE" )]
|
||||
public IReadOnlyList<Certification> Germany { get; set; }
|
||||
|
||||
[DataMember( Name = "IN" )]
|
||||
public IReadOnlyList<Certification> India { get; set; }
|
||||
|
||||
[DataMember( Name = "NZ" )]
|
||||
public IReadOnlyList<Certification> NewZealand { get; set; }
|
||||
|
||||
[DataMember( Name = "US" )]
|
||||
public IReadOnlyList<Certification> UnitedStates { get; set; }
|
||||
|
||||
[DataMember( Name = "GB" )]
|
||||
public IReadOnlyList<Certification> UnitedKingdom { get; set; }
|
||||
|
||||
public MovieRatings()
|
||||
{
|
||||
UnitedStates = Array.Empty<Certification>();
|
||||
Canada = Array.Empty<Certification>();
|
||||
Australia = Array.Empty<Certification>();
|
||||
Germany = Array.Empty<Certification>();
|
||||
France = Array.Empty<Certification>();
|
||||
NewZealand = Array.Empty<Certification>();
|
||||
India = Array.Empty<Certification>();
|
||||
UnitedKingdom = Array.Empty<Certification>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Collections
|
||||
{
|
||||
[DataContract]
|
||||
public class CollectionInfo
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
|
||||
[DataMember( Name = "backdrop_path" )]
|
||||
public string BackdropPath { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if( string.IsNullOrWhiteSpace( Name ) )
|
||||
{
|
||||
return "n/a";
|
||||
}
|
||||
|
||||
return $"{Name} ({Id})";
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
using DM.MovieApi.Shims;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Companies
|
||||
{
|
||||
internal class ApiCompanyRequest : ApiRequestBase, IApiCompanyRequest
|
||||
{
|
||||
private readonly IApiGenreRequest _genreApi;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ApiCompanyRequest( IApiSettings settings, IApiGenreRequest genreApi )
|
||||
: base( settings )
|
||||
{
|
||||
_genreApi = genreApi;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<ProductionCompany>> FindByIdAsync( int companyId )
|
||||
{
|
||||
string command = $"company/{companyId}";
|
||||
|
||||
ApiQueryResponse<ProductionCompany> response = await base.QueryAsync<ProductionCompany>( command );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<MovieInfo>> GetMoviesAsync( int companyId, int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
};
|
||||
|
||||
string command = $"company/{companyId}/movies";
|
||||
|
||||
ApiSearchResponse<MovieInfo> response = await base.SearchAsync<MovieInfo>( command, pageNumber, param );
|
||||
|
||||
if( response.Error != null )
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
response.Results.PopulateGenres( _genreApi );
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Companies
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for retrieving information about a production company.
|
||||
/// </summary>
|
||||
public interface IApiCompanyRequest : IApiRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all the basic information about a specific company.
|
||||
/// </summary>
|
||||
/// <param name="companyId">The company Id is typically found from a Movie or TV query.</param>
|
||||
Task<ApiQueryResponse<ProductionCompany>> FindByIdAsync( int companyId );
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of movies associated with a particular company.
|
||||
/// </summary>
|
||||
/// <param name="companyId">The company Id is typically found from a Movie or TV query.</param>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
/// <returns></returns>
|
||||
Task<ApiSearchResponse<MovieInfo>> GetMoviesAsync( int companyId, int pageNumber = 1, string language = "en" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Companies
|
||||
{
|
||||
[DataContract]
|
||||
public class ParentCompany
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "logo_path" )]
|
||||
public string LogoPath { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if( string.IsNullOrWhiteSpace( Name ) )
|
||||
{
|
||||
return "n/a";
|
||||
}
|
||||
|
||||
return $"{Name} ({Id})";
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Companies
|
||||
{
|
||||
[DataContract]
|
||||
public class ProductionCompany
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "description" )]
|
||||
public string Description { get; set; }
|
||||
|
||||
[DataMember( Name = "headquarters" )]
|
||||
public string Headquarters { get; set; }
|
||||
|
||||
[DataMember( Name = "homepage" )]
|
||||
public string Homepage { get; set; }
|
||||
|
||||
[DataMember( Name = "logo_path" )]
|
||||
public string LogoPath { get; set; }
|
||||
|
||||
[DataMember( Name = "parent_company" )]
|
||||
public ParentCompany ParentCompany { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Name} ({Id})";
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Companies
|
||||
{
|
||||
[DataContract]
|
||||
public class ProductionCompanyInfo : IEqualityComparer<ProductionCompanyInfo>
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
public ProductionCompanyInfo( int id, string name )
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override bool Equals( object obj )
|
||||
{
|
||||
if( obj is not ProductionCompanyInfo info )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals( this, info );
|
||||
}
|
||||
|
||||
public bool Equals( ProductionCompanyInfo x, ProductionCompanyInfo y )
|
||||
=> x != null && y != null && x.Id == y.Id && x.Name == y.Name;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> GetHashCode( this );
|
||||
|
||||
public int GetHashCode( ProductionCompanyInfo obj )
|
||||
{
|
||||
unchecked // Overflow is fine, just wrap
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + obj.Id.GetHashCode();
|
||||
hash = hash * 23 + obj.Name.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if( string.IsNullOrWhiteSpace( Name ) )
|
||||
{
|
||||
return "n/a";
|
||||
}
|
||||
|
||||
return $"{Name} ({Id})";
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Configuration
|
||||
{
|
||||
[DataContract]
|
||||
public class ApiConfiguration
|
||||
{
|
||||
[DataMember( Name = "images" )]
|
||||
public ImageConfiguration Images { get; private set; }
|
||||
|
||||
[DataMember( Name = "change_keys" )]
|
||||
public IReadOnlyList<string> ChangeKeys { get; private set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if( !string.IsNullOrWhiteSpace( Images?.RootUrl ) )
|
||||
{
|
||||
return Images.RootUrl;
|
||||
}
|
||||
|
||||
return "not set";
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.Shims;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Configuration
|
||||
{
|
||||
internal class ApiConfigurationRequest : ApiRequestBase, IApiConfigurationRequest
|
||||
{
|
||||
[ImportingConstructor]
|
||||
public ApiConfigurationRequest( IApiSettings settings )
|
||||
: base( settings )
|
||||
{ }
|
||||
|
||||
public async Task<ApiQueryResponse<ApiConfiguration>> GetAsync()
|
||||
{
|
||||
ApiQueryResponse<ApiConfiguration> response = await base.QueryAsync<ApiConfiguration>( "configuration" );
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for retrieving themoviedb.org configuration information.
|
||||
/// </summary>
|
||||
public interface IApiConfigurationRequest : IApiRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Get themoviedb.org system wide configuration information. Some elements of themoviedb.org
|
||||
/// API require knowledge of the configuration data. The purpose of the <see cref="ApiConfiguration"/>
|
||||
/// is to try and keep the actual API responses as light as possible.</para>
|
||||
/// <para>It is recommended you cache this data within your application and check for updates every few days.
|
||||
/// This method currently holds the data relevant to building image URLs as well as the change key map.</para>
|
||||
/// </summary>
|
||||
Task<ApiQueryResponse<ApiConfiguration>> GetAsync();
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Configuration
|
||||
{
|
||||
[DataContract]
|
||||
public class ImageConfiguration
|
||||
{
|
||||
[DataMember( Name = "base_url" )]
|
||||
public string RootUrl { get; private set; }
|
||||
|
||||
[DataMember( Name = "secure_base_url" )]
|
||||
public string SecureRootUrl { get; private set; }
|
||||
|
||||
[DataMember( Name = "backdrop_sizes" )]
|
||||
public IReadOnlyList<string> BackDrops { get; private set; }
|
||||
|
||||
[DataMember( Name = "logo_sizes" )]
|
||||
public IReadOnlyList<string> Logos { get; private set; }
|
||||
|
||||
[DataMember( Name = "poster_sizes" )]
|
||||
public IReadOnlyList<string> Posters { get; private set; }
|
||||
|
||||
[DataMember( Name = "profile_sizes" )]
|
||||
public IReadOnlyList<string> Profiles { get; private set; }
|
||||
|
||||
[DataMember( Name = "still_sizes" )]
|
||||
public IReadOnlyList<string> Stills { get; private set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if( !string.IsNullOrWhiteSpace( RootUrl ) )
|
||||
{
|
||||
return RootUrl;
|
||||
}
|
||||
|
||||
return "not set";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb
|
||||
{
|
||||
[DataContract]
|
||||
public class Country : IEqualityComparer<Country>
|
||||
{
|
||||
[DataMember( Name = "iso_3166_1" )]
|
||||
public string Iso3166Code { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
public Country( string iso3166Code, string name )
|
||||
{
|
||||
Iso3166Code = iso3166Code;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override bool Equals( object obj )
|
||||
{
|
||||
if( obj is not Country country )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals( this, country );
|
||||
}
|
||||
|
||||
public bool Equals( Country x, Country y )
|
||||
=> x != null && y != null && x.Iso3166Code == y.Iso3166Code && x.Name == y.Name;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
GetHashCode( this );
|
||||
|
||||
public int GetHashCode( Country obj )
|
||||
{
|
||||
unchecked // Overflow is fine, just wrap
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + obj.Iso3166Code.GetHashCode();
|
||||
hash = hash * 23 + obj.Name.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if( string.IsNullOrWhiteSpace( Name ) )
|
||||
{
|
||||
return "n/a";
|
||||
}
|
||||
|
||||
return $"{Name} ({Iso3166Code})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
using DM.MovieApi.Shims;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Genres
|
||||
{
|
||||
internal class ApiGenreRequest : ApiRequestBase, IApiGenreRequest
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly List<Genre> _allGenres = new();
|
||||
|
||||
public IReadOnlyList<Genre> AllGenres
|
||||
{
|
||||
get
|
||||
{
|
||||
if( _allGenres.Any() == false )
|
||||
{
|
||||
var genres = Task.Run( () => GetAllAsync() ).GetAwaiter().GetResult().Item;
|
||||
_allGenres.AddRange( genres );
|
||||
}
|
||||
|
||||
return _allGenres.AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
[ImportingConstructor]
|
||||
public ApiGenreRequest( IApiSettings settings )
|
||||
: base( settings )
|
||||
{ }
|
||||
|
||||
public async Task<ApiQueryResponse<Genre>> FindByIdAsync( int genreId, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language}
|
||||
};
|
||||
|
||||
string command = $"genre/{genreId}";
|
||||
|
||||
ApiQueryResponse<Genre> response = await base.QueryAsync<Genre>( command, param );
|
||||
|
||||
EnsureAllGenres( response );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<IReadOnlyList<Genre>>> GetAllAsync( string language = "en" )
|
||||
{
|
||||
ApiQueryResponse<IReadOnlyList<Genre>> tv = await GetTelevisionAsync( language );
|
||||
if( tv.Error != null )
|
||||
{
|
||||
return tv;
|
||||
}
|
||||
|
||||
ApiQueryResponse<IReadOnlyList<Genre>> movies = await GetMoviesAsync( language );
|
||||
if( movies.Error != null )
|
||||
{
|
||||
return movies;
|
||||
}
|
||||
|
||||
List<Genre> merged = movies.Item
|
||||
.Union( tv.Item )
|
||||
.OrderBy( x => x.Name )
|
||||
.ToList();
|
||||
|
||||
movies.Item = merged.AsReadOnly();
|
||||
|
||||
return movies;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<IReadOnlyList<Genre>>> GetMoviesAsync( string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
};
|
||||
|
||||
ApiQueryResponse<IReadOnlyList<Genre>> genres = await base.QueryAsync( "genre/movie/list", param, GenreDeserializer );
|
||||
|
||||
return genres;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<IReadOnlyList<Genre>>> GetTelevisionAsync( string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
};
|
||||
|
||||
ApiQueryResponse<IReadOnlyList<Genre>> genres = await base.QueryAsync( "genre/tv/list", param, GenreDeserializer );
|
||||
|
||||
return genres;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<MovieInfo>> FindMoviesByIdAsync( int genreId, int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
{"include_adult", "false"},
|
||||
};
|
||||
|
||||
string command = $"genre/{genreId}/movies";
|
||||
|
||||
ApiSearchResponse<MovieInfo> response = await base.SearchAsync<MovieInfo>( command, pageNumber, param );
|
||||
|
||||
if( response.Error != null )
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
response.Results.PopulateGenres( this );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
internal void ClearAllGenres()
|
||||
=> _allGenres.Clear();
|
||||
|
||||
private void EnsureAllGenres( ApiQueryResponse<Genre> response )
|
||||
{
|
||||
if( response.Error != null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( response.Item == null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( _allGenres.Contains( response.Item ) == false )
|
||||
{
|
||||
_allGenres.Add( response.Item );
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<Genre> GenreDeserializer( string json )
|
||||
{
|
||||
var obj = JObject.Parse( json );
|
||||
|
||||
var arr = ( JArray )obj["genres"];
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
var genres = arr.ToObject<IReadOnlyList<Genre>>();
|
||||
|
||||
return genres;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Genres
|
||||
{
|
||||
[DataContract]
|
||||
public class Genre : IEqualityComparer<Genre>
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; private set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Genre( int id, string name )
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override bool Equals( object obj )
|
||||
{
|
||||
if( obj is not Genre genre )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals( this, genre );
|
||||
}
|
||||
|
||||
public bool Equals( Genre x, Genre y )
|
||||
=> x != null && y != null && x.Id == y.Id && x.Name == y.Name;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> GetHashCode( this );
|
||||
|
||||
public int GetHashCode( Genre obj )
|
||||
{
|
||||
unchecked // Overflow is fine, just wrap
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + obj.Id.GetHashCode();
|
||||
hash = hash * 23 + obj.Name.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Name} ({Id})";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Genres
|
||||
{
|
||||
public static class GenreFactory
|
||||
{
|
||||
public static Genre Action()
|
||||
=> new( 28, "Action" );
|
||||
|
||||
public static Genre Adventure()
|
||||
=> new( 12, "Adventure" );
|
||||
|
||||
public static Genre ActionAndAdventure()
|
||||
=> new( 10759, "Action & Adventure" );
|
||||
|
||||
public static Genre Animation()
|
||||
=> new( 16, "Animation" );
|
||||
|
||||
public static Genre Comedy()
|
||||
=> new( 35, "Comedy" );
|
||||
|
||||
public static Genre Crime()
|
||||
=> new( 80, "Crime" );
|
||||
|
||||
public static Genre Drama()
|
||||
=> new( 18, "Drama" );
|
||||
|
||||
public static Genre Documentary()
|
||||
=> new( 99, "Documentary" );
|
||||
|
||||
public static Genre Family()
|
||||
=> new( 10751, "Family" );
|
||||
|
||||
public static Genre Fantasy()
|
||||
=> new( 14, "Fantasy" );
|
||||
|
||||
public static Genre History()
|
||||
=> new( 36, "History" );
|
||||
|
||||
public static Genre Horror()
|
||||
=> new( 27, "Horror" );
|
||||
|
||||
public static Genre Kids()
|
||||
=> new( 10762, "Kids" );
|
||||
|
||||
public static Genre Music()
|
||||
=> new( 10402, "Music" );
|
||||
|
||||
public static Genre Mystery()
|
||||
=> new( 9648, "Mystery" );
|
||||
|
||||
public static Genre News()
|
||||
=> new( 10763, "News" );
|
||||
|
||||
public static Genre Reality()
|
||||
=> new( 10764, "Reality" );
|
||||
|
||||
public static Genre Romance()
|
||||
=> new( 10749, "Romance" );
|
||||
|
||||
public static Genre ScienceFiction()
|
||||
=> new( 878, "Science Fiction" );
|
||||
|
||||
public static Genre SciFiAndFantasy()
|
||||
=> new( 10765, "Sci-Fi & Fantasy" );
|
||||
|
||||
public static Genre Soap()
|
||||
=> new( 10766, "Soap" );
|
||||
|
||||
public static Genre Talk()
|
||||
=> new( 10767, "Talk" );
|
||||
|
||||
public static Genre Thriller()
|
||||
=> new( 53, "Thriller" );
|
||||
|
||||
public static Genre TvMovie()
|
||||
=> new( 10770, "TV Movie" );
|
||||
|
||||
public static Genre War()
|
||||
=> new( 10752, "War" );
|
||||
|
||||
public static Genre WarAndPolitics()
|
||||
=> new( 10768, "War & Politics" );
|
||||
|
||||
public static Genre Western()
|
||||
=> new( 37, "Western" );
|
||||
|
||||
public static IReadOnlyList<Genre> GetAll()
|
||||
=> LazyAll.Value;
|
||||
|
||||
|
||||
private static readonly Lazy<IReadOnlyList<Genre>> LazyAll = new( () =>
|
||||
{
|
||||
var all = typeof( GenreFactory )
|
||||
.GetTypeInfo()
|
||||
.DeclaredMethods
|
||||
.Where( x => x.IsStatic )
|
||||
.Where( x => x.IsPublic )
|
||||
.Where( x => x.ReturnType == typeof( Genre ) )
|
||||
.Select( x => ( Genre )x.Invoke( null, null ) )
|
||||
.ToList();
|
||||
|
||||
return all.AsReadOnly();
|
||||
} );
|
||||
}
|
||||
}
|
||||
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
using DM.MovieApi.MovieDb.People;
|
||||
using DM.MovieApi.MovieDb.TV;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Genres
|
||||
{
|
||||
internal static class GenreIdCollectionMappingExtensions
|
||||
{
|
||||
public static void PopulateGenres( this IEnumerable<MovieInfo> movies, IApiGenreRequest api )
|
||||
{
|
||||
foreach( MovieInfo movie in movies )
|
||||
{
|
||||
movie.Genres = MapGenreIdsToGenres( movie.GenreIds, api );
|
||||
}
|
||||
}
|
||||
|
||||
public static void PopulateGenres( this IEnumerable<TVShowInfo> tvShows, IApiGenreRequest api )
|
||||
{
|
||||
foreach( TVShowInfo tvShow in tvShows )
|
||||
{
|
||||
tvShow.Genres = MapGenreIdsToGenres( tvShow.GenreIds, api );
|
||||
}
|
||||
}
|
||||
|
||||
public static void PopulateGenres( this IEnumerable<PersonInfo> people, IApiGenreRequest api )
|
||||
{
|
||||
foreach( PersonInfo person in people )
|
||||
{
|
||||
foreach( PersonInfoRole role in person.KnownFor )
|
||||
{
|
||||
role.Genres = MapGenreIdsToGenres( role.GenreIds, api );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Genre> MapGenreIdsToGenres( IEnumerable<int> genreIds, IApiGenreRequest api )
|
||||
{
|
||||
IReadOnlyList<Genre> genres = genreIds
|
||||
.Select( x => MapGenre( x, api ) )
|
||||
.ToList()
|
||||
.AsReadOnly();
|
||||
|
||||
return genres;
|
||||
}
|
||||
|
||||
private static Genre MapGenre( int genreId, IApiGenreRequest api )
|
||||
{
|
||||
Genre genre = api.AllGenres.FirstOrDefault( x => x.Id == genreId );
|
||||
|
||||
if( genre == null )
|
||||
{
|
||||
genre = Task.Run( () => api.FindByIdAsync( genreId ) ).GetAwaiter().GetResult().Item;
|
||||
}
|
||||
|
||||
return genre;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Genres
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface representing Movie and TV genres.
|
||||
/// </summary>
|
||||
public interface IApiGenreRequest : IApiRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a cache of all the genres (language='en') returned from <see cref="GetAllAsync"/>.
|
||||
/// As the Genres do not change much, if any, the cache is never evicted.
|
||||
/// </summary>
|
||||
IReadOnlyList<Genre> AllGenres { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the information about a specific Genre.
|
||||
/// </summary>
|
||||
/// <param name="genreId">The genre Id is typically found from a more generic Movie or TV query.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<Genre>> FindByIdAsync( int genreId, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// <para>It is recommended to use the <see cref="AllGenres"/> property, unless a
|
||||
/// language specific parameter other than 'en' is provided.</para>
|
||||
/// <para>
|
||||
/// themoviedb.org api mixes tv and movie genres into their movies and tv titles.
|
||||
/// Use this method to ensure all genres are accounted for when attempting to join
|
||||
/// on Genre.Id from a search result; by default, search results only contain genre
|
||||
/// id and excludes the name.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// In some rare cases, a genre is not included in the movie or tv genres list; when this
|
||||
/// occurs, use the <see cref="FindByIdAsync"/> method to find a matching genre.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <returns>The merged set of Movie and TV Genres.</returns>
|
||||
Task<ApiQueryResponse<IReadOnlyList<Genre>>> GetAllAsync( string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets all movie related Genres.
|
||||
/// </summary>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<IReadOnlyList<Genre>>> GetMoviesAsync( string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tv related Genres.
|
||||
/// </summary>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<IReadOnlyList<Genre>>> GetTelevisionAsync( string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Finds all movies related to a genre, where the Id passed to this method is a genre Id, not a movie Id.
|
||||
/// </summary>
|
||||
/// <param name="genreId">The genre Id is typically found through from a related Movie request or from any of the Genre API methods.</param>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiSearchResponse<MovieInfo>> FindMoviesByIdAsync( int genreId, int pageNumber = 1, string language = "en" );
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.Shims;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.IndustryProfessions
|
||||
{
|
||||
internal class ApiProfessionRequest : ApiRequestBase, IApiProfessionRequest
|
||||
{
|
||||
[ImportingConstructor]
|
||||
public ApiProfessionRequest( IApiSettings settings )
|
||||
: base( settings )
|
||||
{ }
|
||||
|
||||
public Task<ApiQueryResponse<IReadOnlyList<Profession>>> GetAllAsync()
|
||||
{
|
||||
const string command = "job/list";
|
||||
|
||||
Task<ApiQueryResponse<IReadOnlyList<Profession>>> response = base.QueryAsync( command, ProfessionDeserializer );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private IReadOnlyList<Profession> ProfessionDeserializer( string json )
|
||||
{
|
||||
var obj = JObject.Parse( json );
|
||||
|
||||
var arr = ( JArray )obj["jobs"];
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
var professions = arr.ToObject<IReadOnlyList<Profession>>();
|
||||
|
||||
return professions;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.IndustryProfessions
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for retrieving information about Movie/TV industry specific professions.
|
||||
/// </summary>
|
||||
public interface IApiProfessionRequest : IApiRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all the Movie/TV industry specific professions.
|
||||
/// </summary>
|
||||
Task<ApiQueryResponse<IReadOnlyList<Profession>>> GetAllAsync();
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.IndustryProfessions
|
||||
{
|
||||
[DataContract]
|
||||
public class Profession
|
||||
{
|
||||
[DataMember( Name = "department" )]
|
||||
public string Department { get; set; }
|
||||
|
||||
[DataMember( Name = "jobs" )]
|
||||
public IReadOnlyList<string> Jobs { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Department} {Jobs.Count} jobs";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Keywords
|
||||
{
|
||||
[DataContract]
|
||||
public class Keyword : IEqualityComparer<Keyword>
|
||||
{
|
||||
/// <summary>
|
||||
/// The keyword Id as identified by theMovieDB.org.
|
||||
/// </summary>
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The keyword.
|
||||
/// </summary>
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
public Keyword( int id, string name )
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override bool Equals( object obj )
|
||||
{
|
||||
if( obj is not Keyword genre )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals( this, genre );
|
||||
}
|
||||
|
||||
public bool Equals( Keyword x, Keyword y )
|
||||
=> x != null && y != null && x.Id == y.Id && x.Name == y.Name;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> GetHashCode( this );
|
||||
|
||||
public int GetHashCode( Keyword obj )
|
||||
{
|
||||
unchecked // Overflow is fine, just wrap
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + obj.Id.GetHashCode();
|
||||
hash = hash * 23 + obj.Name.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Name} ({Id})";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Keywords
|
||||
{
|
||||
/// <summary>
|
||||
/// Expected parent json node is "keywords". The child node is variable
|
||||
/// and should be set as a parameter to the JsonConverter attribute which
|
||||
/// will use the KeywordConverter .ctor to create the converter with the
|
||||
/// provided parameter.
|
||||
/// </summary>
|
||||
internal class KeywordConverter : JsonConverter
|
||||
{
|
||||
private readonly string _key;
|
||||
|
||||
public KeywordConverter( string key )
|
||||
{
|
||||
_key = key;
|
||||
}
|
||||
|
||||
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
|
||||
{
|
||||
JToken obj = JToken.Load( reader );
|
||||
|
||||
var arr = ( JArray )obj[_key];
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
var keywords = arr.ToObject<IReadOnlyList<Keyword>>();
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
public override bool CanConvert( Type objectType )
|
||||
=> false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb
|
||||
{
|
||||
[DataContract]
|
||||
public class Language : IEqualityComparer<Language>
|
||||
{
|
||||
[DataMember( Name = "iso_639_1" )]
|
||||
public string Iso639Code { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
public Language( string iso639Code, string name )
|
||||
{
|
||||
Iso639Code = iso639Code;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override bool Equals( object obj )
|
||||
{
|
||||
if( obj is not Language language )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals( this, language );
|
||||
}
|
||||
|
||||
public bool Equals( Language x, Language y )
|
||||
=> x != null && y != null && x.Iso639Code == y.Iso639Code && x.Name == y.Name;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> GetHashCode( this );
|
||||
|
||||
public int GetHashCode( Language obj )
|
||||
{
|
||||
unchecked // Overflow is fine, just wrap
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + obj.Iso639Code.GetHashCode();
|
||||
hash = hash * 23 + obj.Name.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if( string.IsNullOrWhiteSpace( Name ) )
|
||||
{
|
||||
return "n/a";
|
||||
}
|
||||
|
||||
return $"{Name} ({Iso639Code})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
using DM.MovieApi.Shims;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Movies
|
||||
{
|
||||
internal class ApiMovieRequest : ApiRequestBase, IApiMovieRequest
|
||||
{
|
||||
private readonly IApiGenreRequest _genreApi;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ApiMovieRequest( IApiSettings settings, IApiGenreRequest genreApi )
|
||||
: base( settings )
|
||||
{
|
||||
_genreApi = genreApi;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<Movie>> FindByIdAsync( int movieId, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
{"append_to_response", "keywords"},
|
||||
};
|
||||
|
||||
string command = $"movie/{movieId}";
|
||||
|
||||
ApiQueryResponse<Movie> response = await base.QueryAsync<Movie>( command, param );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<MovieInfo>> SearchByTitleAsync( string query, int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"query", query},
|
||||
{"include_adult", "false"},
|
||||
{"language", language},
|
||||
};
|
||||
|
||||
const string command = "search/movie";
|
||||
|
||||
ApiSearchResponse<MovieInfo> response = await base.SearchAsync<MovieInfo>( command, pageNumber, param );
|
||||
|
||||
if( response.Error != null )
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
response.Results.PopulateGenres( _genreApi );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<Movie>> GetLatestAsync( string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
{"append_to_response", "keywords"},
|
||||
};
|
||||
|
||||
const string command = "movie/latest";
|
||||
|
||||
ApiQueryResponse<Movie> response = await base.QueryAsync<Movie>( command, param );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<Movie>> GetNowPlayingAsync( int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
{"append_to_response", "keywords"},
|
||||
};
|
||||
|
||||
const string command = "movie/now_playing";
|
||||
|
||||
ApiSearchResponse<Movie> response = await base.SearchAsync<Movie>( command, pageNumber, param );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<Movie>> GetUpcomingAsync( int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
{"append_to_response", "keywords"},
|
||||
};
|
||||
|
||||
const string command = "movie/upcoming";
|
||||
|
||||
ApiSearchResponse<Movie> response = await base.SearchAsync<Movie>( command, pageNumber, param );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<MovieInfo>> GetTopRatedAsync( int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
};
|
||||
|
||||
const string command = "movie/top_rated";
|
||||
|
||||
ApiSearchResponse<MovieInfo> response = await base.SearchAsync<MovieInfo>( command, pageNumber, param );
|
||||
|
||||
if( response.Error != null )
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
response.Results.PopulateGenres( _genreApi );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<MovieInfo>> GetPopularAsync( int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
};
|
||||
|
||||
const string command = "movie/popular";
|
||||
|
||||
ApiSearchResponse<MovieInfo> response = await base.SearchAsync<MovieInfo>( command, pageNumber, param );
|
||||
|
||||
if( response.Error != null )
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
response.Results.PopulateGenres( _genreApi );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<MovieCredit>> GetCreditsAsync( int movieId, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
};
|
||||
|
||||
string command = $"movie/{movieId}/credits";
|
||||
|
||||
ApiQueryResponse<MovieCredit> response = await base.QueryAsync<MovieCredit>( command, param );
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Movies
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for retrieving information about Movies.
|
||||
/// </summary>
|
||||
public interface IApiMovieRequest : IApiRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all the information about a specific Movie.
|
||||
/// </summary>
|
||||
/// <param name="movieId">The movie Id is typically found from a more generic Movie query.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<Movie>> FindByIdAsync( int movieId, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Searches for Movies by title.
|
||||
/// </summary>
|
||||
/// <param name="query">The query to search for Movies.</param>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiSearchResponse<MovieInfo>> SearchByTitleAsync( string query, int pageNumber = 1, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets the most recent movie that has been added to TheMovieDb.org.
|
||||
/// </summary>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<Movie>> GetLatestAsync( string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of movies playing that have been, or are being released this week.
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiSearchResponse<Movie>> GetNowPlayingAsync( int pageNumber = 1, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of upcoming movies by release date.
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiSearchResponse<Movie>> GetUpcomingAsync( int pageNumber = 1, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of top rated movies which is refreshed daily.
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiSearchResponse<MovieInfo>> GetTopRatedAsync( int pageNumber = 1, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of popular movies which is refreshed daily.
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiSearchResponse<MovieInfo>> GetPopularAsync( int pageNumber = 1, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Get the cast and crew information for a specific movie id.
|
||||
/// </summary>
|
||||
/// <param name="movieId">The movie Id is typically found from a more generic Movie query.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<MovieCredit>> GetCreditsAsync( int movieId, string language = "en" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using DM.MovieApi.MovieDb.Collections;
|
||||
using DM.MovieApi.MovieDb.Companies;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
using DM.MovieApi.MovieDb.Keywords;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Movies
|
||||
{
|
||||
[DataContract]
|
||||
public class Movie
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "title" )]
|
||||
public string Title { get; set; }
|
||||
|
||||
[DataMember( Name = "adult" )]
|
||||
public bool IsAdultThemed { get; set; }
|
||||
|
||||
[DataMember( Name = "backdrop_path" )]
|
||||
public string BackdropPath { get; set; }
|
||||
|
||||
[DataMember( Name = "belongs_to_collection" )]
|
||||
public CollectionInfo MovieCollectionInfo { get; set; }
|
||||
|
||||
[DataMember( Name = "budget" )]
|
||||
public int Budget { get; set; }
|
||||
|
||||
[DataMember( Name = "genres" )]
|
||||
public IReadOnlyList<Genre> Genres { get; set; }
|
||||
|
||||
[DataMember( Name = "homepage" )]
|
||||
public string Homepage { get; set; }
|
||||
|
||||
[DataMember( Name = "imdb_id" )]
|
||||
public string ImdbId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ISO 3166-1 code.
|
||||
/// </summary>
|
||||
[DataMember( Name = "original_language" )]
|
||||
public string OriginalLanguage { get; set; }
|
||||
|
||||
[DataMember( Name = "original_title" )]
|
||||
public string OriginalTitle { get; set; }
|
||||
|
||||
[DataMember( Name = "overview" )]
|
||||
public string Overview { get; set; }
|
||||
|
||||
[DataMember( Name = "popularity" )]
|
||||
public double Popularity { get; set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
|
||||
[DataMember( Name = "production_companies" )]
|
||||
public IReadOnlyList<ProductionCompanyInfo> ProductionCompanies { get; set; }
|
||||
|
||||
[DataMember( Name = "production_countries" )]
|
||||
public IReadOnlyList<Country> ProductionCountries { get; set; }
|
||||
|
||||
[DataMember( Name = "release_date" )]
|
||||
public DateTime ReleaseDate { get; set; }
|
||||
|
||||
[DataMember( Name = "revenue" )]
|
||||
public decimal Revenue { get; set; }
|
||||
|
||||
[DataMember( Name = "runtime" )]
|
||||
public int Runtime { get; set; }
|
||||
|
||||
[DataMember( Name = "spoken_languages" )]
|
||||
public IReadOnlyList<Language> SpokenLanguages { get; set; }
|
||||
|
||||
[DataMember( Name = "status" )]
|
||||
public string Status { get; set; }
|
||||
|
||||
[DataMember( Name = "tagline" )]
|
||||
public string Tagline { get; set; }
|
||||
|
||||
[DataMember( Name = "video" )]
|
||||
public bool IsVideo { get; set; }
|
||||
|
||||
[DataMember( Name = "vote_average" )]
|
||||
public double VoteAverage { get; set; }
|
||||
|
||||
[DataMember( Name = "vote_count" )]
|
||||
public int VoteCount { get; set; }
|
||||
|
||||
[DataMember( Name = "keywords" )]
|
||||
[JsonConverter( typeof( KeywordConverter ), "keywords" )]
|
||||
public IReadOnlyList<Keyword> Keywords { get; set; }
|
||||
|
||||
public Movie()
|
||||
{
|
||||
Genres = Array.Empty<Genre>();
|
||||
Keywords = Array.Empty<Keyword>();
|
||||
ProductionCompanies = Array.Empty<ProductionCompanyInfo>();
|
||||
ProductionCountries = Array.Empty<Country>();
|
||||
SpokenLanguages = Array.Empty<Language>();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Title} ({ReleaseDate:yyyy-MM-dd}) [{Id}]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Movies
|
||||
{
|
||||
[DataContract]
|
||||
public class MovieCredit
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int MovieId { get; set; }
|
||||
|
||||
[DataMember( Name = "cast" )]
|
||||
public IReadOnlyList<MovieCastMember> CastMembers { get; set; }
|
||||
|
||||
[DataMember( Name = "crew" )]
|
||||
public IReadOnlyList<MovieCrewMember> CrewMembers { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class MovieCastMember
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int PersonId { get; set; }
|
||||
|
||||
[DataMember( Name = "cast_id" )]
|
||||
public int CastId { get; set; }
|
||||
|
||||
[DataMember( Name = "credit_id" )]
|
||||
public string CreditId { get; set; }
|
||||
|
||||
[DataMember( Name = "character" )]
|
||||
public string Character { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "order" )]
|
||||
public int Order { get; set; }
|
||||
|
||||
[DataMember( Name = "profile_path" )]
|
||||
public string ProfilePath { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Character}: {Name}";
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class MovieCrewMember
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int PersonId { get; set; }
|
||||
|
||||
[DataMember( Name = "credit_id" )]
|
||||
public string CreditId { get; set; }
|
||||
|
||||
[DataMember( Name = "department" )]
|
||||
public string Department { get; set; }
|
||||
|
||||
[DataMember( Name = "job" )]
|
||||
public string Job { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "profile_path" )]
|
||||
public string ProfilePath { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Name} | {Department} | {Job}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.Movies
|
||||
{
|
||||
[DataContract]
|
||||
public class MovieInfo
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "title" )]
|
||||
public string Title { get; set; }
|
||||
|
||||
[DataMember( Name = "adult" )]
|
||||
public bool IsAdultThemed { get; set; }
|
||||
|
||||
[DataMember( Name = "backdrop_path" )]
|
||||
public string BackdropPath { get; set; }
|
||||
|
||||
[DataMember( Name = "genre_ids" )]
|
||||
internal IReadOnlyList<int> GenreIds { get; set; }
|
||||
|
||||
public IReadOnlyList<Genre> Genres { get; set; }
|
||||
|
||||
[DataMember( Name = "original_title" )]
|
||||
public string OriginalTitle { get; set; }
|
||||
|
||||
[DataMember( Name = "overview" )]
|
||||
public string Overview { get; set; }
|
||||
|
||||
[DataMember( Name = "release_date" )]
|
||||
public DateTime ReleaseDate { get; set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
|
||||
[DataMember( Name = "popularity" )]
|
||||
public double Popularity { get; set; }
|
||||
|
||||
[DataMember( Name = "video" )]
|
||||
public bool Video { get; set; }
|
||||
|
||||
[DataMember( Name = "vote_average" )]
|
||||
public double VoteAverage { get; set; }
|
||||
|
||||
[DataMember( Name = "vote_count" )]
|
||||
public int VoteCount { get; set; }
|
||||
|
||||
public MovieInfo()
|
||||
{
|
||||
GenreIds = Array.Empty<int>();
|
||||
Genres = Array.Empty<Genre>();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Title} ({Id} - {ReleaseDate:yyyy-MM-dd})";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
using DM.MovieApi.Shims;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.People
|
||||
{
|
||||
internal class ApiPeopleRequest : ApiRequestBase, IApiPeopleRequest
|
||||
{
|
||||
private readonly IApiGenreRequest _genreApi;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ApiPeopleRequest( IApiSettings settings, IApiGenreRequest genreApi )
|
||||
: base( settings )
|
||||
{
|
||||
_genreApi = genreApi;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<Person>> FindByIdAsync( int personId, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language}
|
||||
};
|
||||
|
||||
string command = $"person/{personId}";
|
||||
|
||||
ApiQueryResponse<Person> response = await base.QueryAsync<Person>( command, param );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<PersonInfo>> SearchByNameAsync( string query, int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"query", query},
|
||||
{"include_adult", "false"},
|
||||
{"language", language},
|
||||
};
|
||||
|
||||
const string command = "search/person";
|
||||
|
||||
ApiSearchResponse<PersonInfo> response = await base.SearchAsync<PersonInfo>( command, pageNumber, param );
|
||||
|
||||
if( response.Error != null )
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
response.Results.PopulateGenres( _genreApi );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<PersonMovieCredit>> GetMovieCreditsAsync( int personId, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
};
|
||||
|
||||
string command = $"person/{personId}/movie_credits";
|
||||
|
||||
ApiQueryResponse<PersonMovieCredit> response = await base.QueryAsync<PersonMovieCredit>( command, param );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<PersonTVCredit>> GetTVCreditsAsync( int personId, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{"language", language},
|
||||
};
|
||||
|
||||
string command = $"person/{personId}/tv_credits";
|
||||
|
||||
ApiQueryResponse<PersonTVCredit> response = await base.QueryAsync<PersonTVCredit>( command, param );
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DM.MovieApi.MovieDb.People
|
||||
{
|
||||
public enum Gender
|
||||
{
|
||||
Unknown = 0,
|
||||
Female = 1,
|
||||
Male = 2,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.People
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for retrieving information about People.
|
||||
/// </summary>
|
||||
public interface IApiPeopleRequest : IApiRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all the information about a specific Person.
|
||||
/// </summary>
|
||||
/// <param name="personId">The person Id is typically found from a more generic query such as movie or television or search.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<Person>> FindByIdAsync( int personId, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Searches for People by name.
|
||||
/// </summary>
|
||||
/// <param name="query">The query to search for People.</param>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiSearchResponse<PersonInfo>> SearchByNameAsync( string query, int pageNumber = 1, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Get the movie credits for a specific person id. Includes movie cast and crew information for the person.
|
||||
/// </summary>
|
||||
/// <param name="personId">The person Id is typically found from a more generic query such as movie or television or search.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<PersonMovieCredit>> GetMovieCreditsAsync( int personId, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Get the television credits for a specific person id. Includes TV cast and crew information for the person.
|
||||
/// </summary>
|
||||
/// <param name="personId">The person Id is typically found from a more generic query such as movie or television or search.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<PersonTVCredit>> GetTVCreditsAsync( int personId, string language = "en" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.People
|
||||
{
|
||||
[DataContract]
|
||||
public class Person
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "also_known_as" )]
|
||||
public IReadOnlyList<string> AlsoKnownAs { get; set; }
|
||||
|
||||
[DataMember( Name = "adult" )]
|
||||
public bool IsAdultFilmStar { get; set; }
|
||||
|
||||
[DataMember( Name = "biography" )]
|
||||
public string Biography { get; set; }
|
||||
|
||||
[DataMember( Name = "birthday" )]
|
||||
public DateTime Birthday { get; set; }
|
||||
|
||||
[DataMember( Name = "deathday" )]
|
||||
public DateTime? Deathday { get; set; }
|
||||
|
||||
[DataMember( Name = "gender" )]
|
||||
public Gender Gender { get; set; }
|
||||
|
||||
[DataMember( Name = "homepage" )]
|
||||
public string Homepage { get; set; }
|
||||
|
||||
[DataMember( Name = "imdb_id" )]
|
||||
public string ImdbId { get; set; }
|
||||
|
||||
[DataMember( Name = "place_of_birth" )]
|
||||
public string PlaceOfBirth { get; set; }
|
||||
|
||||
[DataMember( Name = "popularity" )]
|
||||
public double Popularity { get; set; }
|
||||
|
||||
[DataMember( Name = "profile_path" )]
|
||||
public string ProfilePath { get; set; }
|
||||
|
||||
public Person()
|
||||
{
|
||||
AlsoKnownAs = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.People
|
||||
{
|
||||
public enum MediaType
|
||||
{
|
||||
Unknown,
|
||||
Movie,
|
||||
TV,
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class PersonInfo
|
||||
{
|
||||
// TODO: (K. Chase) [2016-07-10] Update all POCO's to explicitly name the Id property, i.e,. PersonId, MovieId, TVShowId.
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "adult" )]
|
||||
public bool IsAdultFilmStar { get; set; }
|
||||
|
||||
[DataMember( Name = "known_for" )]
|
||||
public IReadOnlyList<PersonInfoRole> KnownFor { get; set; }
|
||||
|
||||
[DataMember( Name = "profile_path" )]
|
||||
public string ProfilePath { get; set; }
|
||||
|
||||
[DataMember( Name = "popularity" )]
|
||||
public double Popularity { get; set; }
|
||||
|
||||
public PersonInfo()
|
||||
{
|
||||
KnownFor = Array.Empty<PersonInfoRole>();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Name} ({Id})";
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class PersonInfoRole
|
||||
{
|
||||
// TODO: (K. Chase) [2016-07-10] Break into type for Movie and TV w/ a custom serializer.
|
||||
// re: see TVShowName v MovieTitle (and related)
|
||||
|
||||
/// <summary>
|
||||
/// The MovieId or TVShowId as defined by the value of <see cref="MediaType"/>.
|
||||
/// </summary>
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "media_type" )]
|
||||
public MediaType MediaType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only populated when <see cref="MediaType"/> is TV.
|
||||
/// </summary>
|
||||
[DataMember( Name = "name" )]
|
||||
public string TVShowName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only populated when <see cref="MediaType"/> is TV.
|
||||
/// </summary>
|
||||
[DataMember( Name = "original_name" )]
|
||||
public string TVShowOriginalName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only populated when <see cref="MediaType"/> is Movie.
|
||||
/// </summary>
|
||||
[DataMember( Name = "title" )]
|
||||
public string MovieTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only populated when <see cref="MediaType"/> is Movie.
|
||||
/// </summary>
|
||||
[DataMember( Name = "original_title" )]
|
||||
public string MovieOriginalTitle { set; get; }
|
||||
|
||||
[DataMember( Name = "backdrop_path" )]
|
||||
public string BackdropPath { get; set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only populated when <see cref="MediaType"/> is Movie.
|
||||
/// </summary>
|
||||
[DataMember( Name = "release_date" )]
|
||||
public DateTime MovieReleaseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only populated when <see cref="MediaType"/> is TV.
|
||||
/// </summary>
|
||||
[DataMember( Name = "first_air_date" )]
|
||||
public DateTime TVShowFirstAirDate { get; set; }
|
||||
|
||||
[DataMember( Name = "overview" )]
|
||||
public string Overview { get; set; }
|
||||
|
||||
[DataMember( Name = "adult" )]
|
||||
public bool IsAdultThemed { get; set; }
|
||||
|
||||
[DataMember( Name = "video" )]
|
||||
public bool IsVideo { get; set; }
|
||||
|
||||
[DataMember( Name = "genre_ids" )]
|
||||
internal IReadOnlyList<int> GenreIds { get; set; }
|
||||
|
||||
public IReadOnlyList<Genre> Genres { get; set; }
|
||||
|
||||
[DataMember( Name = "original_language" )]
|
||||
public string OriginalLanguage { get; set; }
|
||||
|
||||
[DataMember( Name = "popularity" )]
|
||||
public double Popularity { get; set; }
|
||||
|
||||
[DataMember( Name = "vote_count" )]
|
||||
public int VoteCount { get; set; }
|
||||
|
||||
[DataMember( Name = "vote_average" )]
|
||||
public double VoteAverage { get; set; }
|
||||
|
||||
[DataMember( Name = "origin_country" )]
|
||||
public IReadOnlyList<string> OriginCountry { get; set; }
|
||||
|
||||
public PersonInfoRole()
|
||||
{
|
||||
GenreIds = Array.Empty<int>();
|
||||
Genres = Array.Empty<Genre>();
|
||||
OriginCountry = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return MediaType == MediaType.Movie
|
||||
? $"Movie: {MovieTitle} ({Id} - {MovieReleaseDate:yyyy-MM-dd})"
|
||||
: $"TV: {TVShowName} ({Id} - {TVShowFirstAirDate:yyyy-MM-dd})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.People
|
||||
{
|
||||
[DataContract]
|
||||
public class PersonMovieCredit
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int PersonId { get; set; }
|
||||
|
||||
[DataMember( Name = "cast" )]
|
||||
public IReadOnlyList<PersonMovieCastMember> CastRoles { get; set; }
|
||||
|
||||
[DataMember( Name = "crew" )]
|
||||
public IReadOnlyList<PersonMovieCrewMember> CrewRoles { get; set; }
|
||||
|
||||
public PersonMovieCredit()
|
||||
{
|
||||
CastRoles = Array.Empty<PersonMovieCastMember>();
|
||||
CrewRoles = Array.Empty<PersonMovieCrewMember>();
|
||||
}
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class PersonMovieCastMember
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int MovieId { get; set; }
|
||||
|
||||
[DataMember( Name = "adult" )]
|
||||
public bool IsAdultThemed { get; set; }
|
||||
|
||||
[DataMember( Name = "character" )]
|
||||
public string Character { get; set; }
|
||||
|
||||
[DataMember( Name = "credit_id" )]
|
||||
public string CreditId { get; set; }
|
||||
|
||||
[DataMember( Name = "original_title" )]
|
||||
public string OriginalTitle { get; set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
|
||||
[DataMember( Name = "release_date" )]
|
||||
public DateTime ReleaseDate { get; set; }
|
||||
|
||||
[DataMember( Name = "title" )]
|
||||
public string Title { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class PersonMovieCrewMember
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int MovieId { get; set; }
|
||||
|
||||
[DataMember( Name = "adult" )]
|
||||
public bool IsAdultThemed { get; set; }
|
||||
|
||||
[DataMember( Name = "credit_id" )]
|
||||
public string CreditId { get; set; }
|
||||
|
||||
[DataMember( Name = "department" )]
|
||||
public string Department { get; set; }
|
||||
|
||||
[DataMember( Name = "job" )]
|
||||
public string Job { get; set; }
|
||||
|
||||
[DataMember( Name = "original_title" )]
|
||||
public string OriginalTitle { get; set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
|
||||
[DataMember( Name = "release_date" )]
|
||||
public DateTime ReleaseDate { get; set; }
|
||||
|
||||
[DataMember( Name = "title" )]
|
||||
public string Title { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.People
|
||||
{
|
||||
[DataContract]
|
||||
public class PersonTVCredit
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int PersonId { get; set; }
|
||||
|
||||
[DataMember( Name = "cast" )]
|
||||
public IReadOnlyList<PersonTVCastMember> CastRoles { get; set; }
|
||||
|
||||
[DataMember( Name = "crew" )]
|
||||
public IReadOnlyList<PersonTVCrewMember> CrewRoles { get; set; }
|
||||
|
||||
public PersonTVCredit()
|
||||
{
|
||||
CastRoles = Array.Empty<PersonTVCastMember>();
|
||||
CrewRoles = Array.Empty<PersonTVCrewMember>();
|
||||
}
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class PersonTVCastMember
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int TVShowId { get; set; }
|
||||
|
||||
[DataMember( Name = "character" )]
|
||||
public string Character { get; set; }
|
||||
|
||||
[DataMember( Name = "credit_id" )]
|
||||
public string CreditId { get; set; }
|
||||
|
||||
[DataMember( Name = "episode_count" )]
|
||||
public int EpisodeCount { get; set; }
|
||||
|
||||
[DataMember( Name = "first_air_date" )]
|
||||
public DateTime FirstAirDate { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "original_name" )]
|
||||
public string OriginalName { get; set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class PersonTVCrewMember
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int TVShowId { get; set; }
|
||||
|
||||
[DataMember( Name = "credit_id" )]
|
||||
public string CreditId { get; set; }
|
||||
|
||||
[DataMember( Name = "department" )]
|
||||
public string Department { get; set; }
|
||||
|
||||
[DataMember( Name = "episode_count" )]
|
||||
public int EpisodeCount { get; set; }
|
||||
|
||||
[DataMember( Name = "first_air_date" )]
|
||||
public DateTime FirstAirDate { get; set; }
|
||||
|
||||
[DataMember( Name = "job" )]
|
||||
public string Job { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "original_name" )]
|
||||
public string OriginalName { get; set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
using DM.MovieApi.Shims;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.TV
|
||||
{
|
||||
internal class ApiTVShowRequest : ApiRequestBase, IApiTVShowRequest
|
||||
{
|
||||
private readonly IApiGenreRequest _genreApi;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ApiTVShowRequest( IApiSettings settings, IApiGenreRequest genreApi )
|
||||
: base( settings )
|
||||
{
|
||||
_genreApi = genreApi;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<TVShow>> FindByIdAsync( int tvShowId, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{ "language", language },
|
||||
{ "append_to_response", "keywords" },
|
||||
};
|
||||
|
||||
string command = $"tv/{tvShowId}";
|
||||
|
||||
ApiQueryResponse<TVShow> response = await base.QueryAsync<TVShow>( command, param );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<TVShowInfo>> SearchByNameAsync( string query, int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{ "query", query },
|
||||
{ "language", language }
|
||||
};
|
||||
|
||||
const string command = "search/tv";
|
||||
|
||||
ApiSearchResponse<TVShowInfo> response = await base.SearchAsync<TVShowInfo>( command, pageNumber, param );
|
||||
|
||||
if( response.Error != null )
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
response.Results.PopulateGenres( _genreApi );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiQueryResponse<TVShow>> GetLatestAsync( string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{ "language", language },
|
||||
{ "append_to_response", "keywords" },
|
||||
};
|
||||
|
||||
const string command = "tv/latest";
|
||||
|
||||
ApiQueryResponse<TVShow> response = await base.QueryAsync<TVShow>( command, param );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<TVShowInfo>> GetTopRatedAsync( int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{ "language", language }
|
||||
};
|
||||
|
||||
const string command = "tv/top_rated";
|
||||
|
||||
ApiSearchResponse<TVShowInfo> response = await base.SearchAsync<TVShowInfo>( command, pageNumber, param );
|
||||
|
||||
if( response.Error != null )
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
response.Results.PopulateGenres( _genreApi );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<ApiSearchResponse<TVShowInfo>> GetPopularAsync( int pageNumber = 1, string language = "en" )
|
||||
{
|
||||
var param = new Dictionary<string, string>
|
||||
{
|
||||
{ "language", language }
|
||||
};
|
||||
|
||||
const string command = "tv/popular";
|
||||
|
||||
ApiSearchResponse<TVShowInfo> response = await base.SearchAsync<TVShowInfo>( command, pageNumber, param );
|
||||
|
||||
if( response.Error != null )
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
response.Results.PopulateGenres( _genreApi );
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Threading.Tasks;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.ApiResponse;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.TV
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for retrieving information about TV shows.
|
||||
/// </summary>
|
||||
public interface IApiTVShowRequest : IApiRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all the information about a specific TV show.
|
||||
/// </summary>
|
||||
/// <param name="tvShowId">The TV show Id which is typically found from a more generic TV show query.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<TVShow>> FindByIdAsync( int tvShowId, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Searches for TV shows by title.
|
||||
/// </summary>
|
||||
/// <param name="query">The query to search for TV shows.</param>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiSearchResponse<TVShowInfo>> SearchByNameAsync( string query, int pageNumber = 1, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest TV show added to TheMovieDb.org
|
||||
/// </summary>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiQueryResponse<TVShow>> GetLatestAsync( string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of top rated TV shows which is refreshed daily.
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiSearchResponse<TVShowInfo>> GetTopRatedAsync( int pageNumber = 1, string language = "en" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of popular TV shows which is refreshed daily.
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">Default is page 1. The page number to retrieve; the <see cref="ApiSearchResponse{T}"/> will contain the current page returned and the total number of pages available.</param>
|
||||
/// <param name="language">Default is English. The ISO 639-1 language code to retrieve the result from.</param>
|
||||
Task<ApiSearchResponse<TVShowInfo>> GetPopularAsync( int pageNumber = 1, string language = "en" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.TV
|
||||
{
|
||||
[DataContract]
|
||||
public class Network : IEqualityComparer<Network>
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
public Network( int id, string name )
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public bool Equals( Network x, Network y )
|
||||
=> x != null && y != null && x.Id == y.Id && x.Name == y.Name;
|
||||
|
||||
public int GetHashCode( Network obj )
|
||||
{
|
||||
unchecked // Overflow is fine, just wrap
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + obj.Id.GetHashCode();
|
||||
hash = hash * 23 + obj.Name.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals( object obj )
|
||||
{
|
||||
if( obj is not Network network )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals( this, network );
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
=> GetHashCode( this );
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Name} ({Id})";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.TV
|
||||
{
|
||||
[DataContract]
|
||||
public class Season
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "air_date" )]
|
||||
public DateTime AirDate { get; set; }
|
||||
|
||||
[DataMember( Name = "episode_count" )]
|
||||
public int EpisodeCount { get; set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
|
||||
[DataMember( Name = "season_number" )]
|
||||
public int SeasonNumber { get; set; }
|
||||
|
||||
public Season( int id, DateTime airDate, int episodeCount, string posterPath, int seasonNumber )
|
||||
{
|
||||
Id = id;
|
||||
AirDate = airDate;
|
||||
EpisodeCount = episodeCount;
|
||||
PosterPath = posterPath;
|
||||
SeasonNumber = seasonNumber;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"({SeasonNumber} - {AirDate:yyyy-MM-dd})";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using DM.MovieApi.MovieDb.Companies;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
using DM.MovieApi.MovieDb.Keywords;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.TV
|
||||
{
|
||||
[DataContract]
|
||||
public class TVShow
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "backdrop_path" )]
|
||||
public string BackdropPath { get; set; }
|
||||
|
||||
[DataMember( Name = "created_by" )]
|
||||
public IReadOnlyList<TVShowCreator> CreatedBy { get; set; }
|
||||
|
||||
[DataMember( Name = "episode_run_time" )]
|
||||
public IReadOnlyList<int> EpisodeRunTime { get; set; }
|
||||
|
||||
[DataMember( Name = "first_air_date" )]
|
||||
public DateTime FirstAirDate { get; set; }
|
||||
|
||||
[DataMember( Name = "genres" )]
|
||||
public IReadOnlyList<Genre> Genres { get; set; }
|
||||
|
||||
[DataMember( Name = "homepage" )]
|
||||
public string Homepage { get; set; }
|
||||
|
||||
[DataMember( Name = "in_production" )]
|
||||
public bool InProduction { get; set; }
|
||||
|
||||
[DataMember( Name = "languages" )]
|
||||
public IReadOnlyList<string> Languages { get; set; }
|
||||
|
||||
[DataMember( Name = "last_air_date" )]
|
||||
public DateTime LastAirDate { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "networks" )]
|
||||
public IReadOnlyList<Network> Networks { get; set; }
|
||||
|
||||
[DataMember( Name = "number_of_episodes" )]
|
||||
public int NumberOfEpisodes { get; set; }
|
||||
|
||||
[DataMember( Name = "number_of_seasons" )]
|
||||
public int NumberOfSeasons { get; set; }
|
||||
|
||||
[DataMember( Name = "origin_country" )]
|
||||
public IReadOnlyList<string> OriginCountry { get; set; }
|
||||
|
||||
[DataMember( Name = "original_language" )]
|
||||
public string OriginalLanguage { get; set; }
|
||||
|
||||
[DataMember( Name = "original_name" )]
|
||||
public string OriginalName { get; set; }
|
||||
|
||||
[DataMember( Name = "overview" )]
|
||||
public string Overview { get; set; }
|
||||
|
||||
[DataMember( Name = "popularity" )]
|
||||
public double Popularity { get; set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
|
||||
[DataMember( Name = "production_companies" )]
|
||||
public IReadOnlyList<ProductionCompanyInfo> ProductionCompanies { get; set; }
|
||||
|
||||
[DataMember( Name = "seasons" )]
|
||||
public IReadOnlyList<Season> Seasons { get; set; }
|
||||
|
||||
[DataMember( Name = "keywords" )]
|
||||
[JsonConverter( typeof( KeywordConverter ), "results" )]
|
||||
public IReadOnlyList<Keyword> Keywords { get; set; }
|
||||
|
||||
public TVShow()
|
||||
{
|
||||
CreatedBy = Array.Empty<TVShowCreator>();
|
||||
EpisodeRunTime = Array.Empty<int>();
|
||||
Genres = Array.Empty<Genre>();
|
||||
Languages = Array.Empty<string>();
|
||||
Networks = Array.Empty<Network>();
|
||||
OriginCountry = Array.Empty<string>();
|
||||
ProductionCompanies = Array.Empty<ProductionCompanyInfo>();
|
||||
Seasons = Array.Empty<Season>();
|
||||
Keywords = Array.Empty<Keyword>();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Name} ({FirstAirDate:yyyy-MM-dd}) [{Id}]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace DM.MovieApi.MovieDb.TV
|
||||
{
|
||||
[DataContract]
|
||||
public class TVShowCreator : IEqualityComparer<TVShowCreator>
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember( Name = "profile_path" )]
|
||||
public string ProfilePath { get; set; }
|
||||
|
||||
public TVShowCreator( int id, string name, string profilePath )
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
ProfilePath = profilePath;
|
||||
}
|
||||
|
||||
public bool Equals( TVShowCreator x, TVShowCreator y )
|
||||
=> x != null && y != null && x.Id == y.Id && x.Name == y.Name;
|
||||
|
||||
public int GetHashCode( TVShowCreator obj )
|
||||
{
|
||||
unchecked // Overflow is fine, just wrap
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + obj.Id.GetHashCode();
|
||||
hash = hash * 23 + obj.Name.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals( object obj )
|
||||
{
|
||||
if( obj is not TVShowCreator showCreator )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals( this, showCreator );
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
=> GetHashCode( this );
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Name} ({Id})";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
||||
|
||||
namespace DM.MovieApi.MovieDb.TV
|
||||
{
|
||||
[DataContract]
|
||||
public class TVShowInfo
|
||||
{
|
||||
[DataMember( Name = "id" )]
|
||||
public int Id { get; private set; }
|
||||
|
||||
[DataMember( Name = "name" )]
|
||||
public string Name { get; private set; }
|
||||
|
||||
[DataMember( Name = "original_name" )]
|
||||
public string OriginalName { get; private set; }
|
||||
|
||||
[DataMember( Name = "poster_path" )]
|
||||
public string PosterPath { get; set; }
|
||||
|
||||
[DataMember( Name = "backdrop_path" )]
|
||||
public string BackdropPath { get; set; }
|
||||
|
||||
[DataMember( Name = "popularity" )]
|
||||
public double Popularity { get; private set; }
|
||||
|
||||
[DataMember( Name = "vote_average" )]
|
||||
public double VoteAverage { get; private set; }
|
||||
|
||||
[DataMember( Name = "vote_count" )]
|
||||
public int VoteCount { get; private set; }
|
||||
|
||||
[DataMember( Name = "overview" )]
|
||||
public string Overview { get; private set; }
|
||||
|
||||
[DataMember( Name = "first_air_date" )]
|
||||
public DateTime FirstAirDate { get; private set; }
|
||||
|
||||
[DataMember( Name = "origin_country" )]
|
||||
public IReadOnlyList<string> OriginCountry { get; private set; }
|
||||
|
||||
[DataMember( Name = "genre_ids" )]
|
||||
internal IReadOnlyList<int> GenreIds { get; set; }
|
||||
|
||||
public IReadOnlyList<Genre> Genres { get; internal set; }
|
||||
|
||||
[DataMember( Name = "original_language" )]
|
||||
public string OriginalLanguage { get; private set; }
|
||||
|
||||
public TVShowInfo()
|
||||
{
|
||||
OriginCountry = Array.Empty<string>();
|
||||
GenreIds = Array.Empty<int>();
|
||||
Genres = Array.Empty<Genre>();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Name} ({Id} - {FirstAirDate:yyyy-MM-dd})";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using DM.MovieApi.ApiRequest;
|
||||
using DM.MovieApi.MovieDb.Genres;
|
||||
using DM.MovieApi.Shims;
|
||||
|
||||
[assembly: InternalsVisibleTo( "DM.MovieApi.IntegrationTests" )]
|
||||
|
||||
namespace DM.MovieApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Note: one of the RegisterSettings must be called before the Factory can Create anything.
|
||||
/// </summary>
|
||||
public static class MovieDbFactory
|
||||
{
|
||||
/// <include file='ApiDocs.xml' path='Doc/ApiSettings/ApiUrl/*'/>
|
||||
public const string TheMovieDbApiUrl = "http://api.themoviedb.org/3/";
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the underlying factory has been created.
|
||||
/// </summary>
|
||||
public static bool IsFactoryComposed => Settings != null;
|
||||
|
||||
internal static IApiSettings Settings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Registers themoviedb.org settings for use with the internal DI container.
|
||||
/// </summary>
|
||||
/// <param name="bearerToken">
|
||||
/// <include file='ApiDocs.xml' path='Doc/ApiSettings/BearerToken/summary/*'/>
|
||||
/// </param>
|
||||
public static void RegisterSettings( string bearerToken )
|
||||
{
|
||||
ResetFactory();
|
||||
|
||||
if( bearerToken is null || bearerToken.Length <= 200 )
|
||||
{
|
||||
// v3 access key was approx 33 chars; v4 bearer is approx 212 chars.
|
||||
throw new ArgumentException(
|
||||
$"Must provide a valid TheMovieDb.org Bearer token. Invalid: {bearerToken}. " +
|
||||
"A valid token can be found in your account page, under the API section. " +
|
||||
"You will see a new key listed under the header \"API Read Access Token\".", bearerToken );
|
||||
}
|
||||
|
||||
Settings = new MovieDbSettings( TheMovieDbApiUrl, bearerToken );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Creates the specific API requested.</para>
|
||||
/// <para><inheritdoc cref="MovieDbFactory" path="/summary"/></para>
|
||||
/// </summary>
|
||||
public static Lazy<T> Create<T>() where T : IApiRequest
|
||||
{
|
||||
ContainerGuard();
|
||||
|
||||
var requestResolver = new ApiRequestResolver();
|
||||
|
||||
return new Lazy<T>( requestResolver.Get<T> );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Creates a global instance exposing all API interfaces against themoviedb.org
|
||||
/// that are currently available in this release. Each API is exposed via a Lazy property
|
||||
/// ensuring no objects are created until they are needed.</para>
|
||||
/// <para><inheritdoc cref="MovieDbFactory" path="/summary"/></para>
|
||||
/// </summary>
|
||||
public static IMovieDbApi GetAllApiRequests()
|
||||
{
|
||||
ContainerGuard();
|
||||
|
||||
// Note: the concrete implementation is currently excluded from the .csproj, but is still included in source control.
|
||||
|
||||
string msg = $"{nameof( GetAllApiRequests )} has been temporarily disabled due to porting the code base to Asp.Net Core to provide support for portable library projects.";
|
||||
throw new NotImplementedException( msg );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all factory settings; forces the next call to be RegisterSettings.
|
||||
/// before <see cref="Create{T}"/> can be called.
|
||||
/// </summary>
|
||||
public static void ResetFactory()
|
||||
{
|
||||
Settings = null;
|
||||
}
|
||||
|
||||
private static void ContainerGuard()
|
||||
{
|
||||
if( !IsFactoryComposed )
|
||||
{
|
||||
throw new InvalidOperationException( $"{nameof( RegisterSettings )} must be called before the Factory can Create anything." );
|
||||
}
|
||||
}
|
||||
|
||||
private class MovieDbSettings : IApiSettings
|
||||
{
|
||||
public string ApiUrl { get; }
|
||||
public string BearerToken { get; }
|
||||
|
||||
public MovieDbSettings( string apiUrl, string bearerToken )
|
||||
{
|
||||
ApiUrl = apiUrl;
|
||||
BearerToken = bearerToken;
|
||||
}
|
||||
}
|
||||
|
||||
private class ApiRequestResolver
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<Type, Func<object>> SupportedDependencyTypeMap;
|
||||
private static readonly ConcurrentDictionary<Type, ConstructorInfo> TypeCtorMap;
|
||||
|
||||
static ApiRequestResolver()
|
||||
{
|
||||
SupportedDependencyTypeMap = new Dictionary<Type, Func<object>>
|
||||
{
|
||||
{typeof(IApiSettings), () => Settings},
|
||||
{typeof(IApiGenreRequest), () => new ApiGenreRequest( Settings )}
|
||||
};
|
||||
|
||||
TypeCtorMap = new ConcurrentDictionary<Type, ConstructorInfo>();
|
||||
}
|
||||
|
||||
public T Get<T>() where T : IApiRequest
|
||||
{
|
||||
ConstructorInfo ctor = TypeCtorMap.GetOrAdd( typeof( T ), GetConstructor );
|
||||
|
||||
ParameterInfo[] param = ctor.GetParameters();
|
||||
|
||||
if( param.Length == 0 )
|
||||
{
|
||||
return ( T )ctor.Invoke( null );
|
||||
}
|
||||
|
||||
var paramObjects = new List<object>( param.Length );
|
||||
foreach( ParameterInfo p in param )
|
||||
{
|
||||
if( SupportedDependencyTypeMap.ContainsKey( p.ParameterType ) == false )
|
||||
{
|
||||
throw new InvalidOperationException( $"{p.ParameterType.FullName} is not a supported dependency type for {typeof( T ).FullName}." );
|
||||
}
|
||||
|
||||
Func<object> typeResolver = SupportedDependencyTypeMap[p.ParameterType];
|
||||
|
||||
paramObjects.Add( typeResolver() );
|
||||
}
|
||||
|
||||
return ( T )ctor.Invoke( paramObjects.ToArray() );
|
||||
}
|
||||
|
||||
private ConstructorInfo GetConstructor( Type t )
|
||||
{
|
||||
ConstructorInfo[] ctors = GetAvailableConstructors( t.GetTypeInfo() );
|
||||
|
||||
if( ctors.Length == 0 )
|
||||
{
|
||||
throw new InvalidOperationException( $"No public constructors found for {t.FullName}." );
|
||||
}
|
||||
|
||||
if( ctors.Length == 1 )
|
||||
{
|
||||
return ctors[0];
|
||||
}
|
||||
|
||||
var importingCtors = ctors
|
||||
.Where( x => x.IsDefined( typeof( ImportingConstructorAttribute ) ) )
|
||||
.ToArray();
|
||||
|
||||
if( importingCtors.Length != 1 )
|
||||
{
|
||||
throw new InvalidOperationException( "Multiple public constructors found. " +
|
||||
$"One must be decorated with the {nameof( ImportingConstructorAttribute )}." );
|
||||
}
|
||||
|
||||
return importingCtors[0];
|
||||
}
|
||||
|
||||
private ConstructorInfo[] GetAvailableConstructors( TypeInfo typeInfo )
|
||||
{
|
||||
TypeInfo[] implementingTypes = typeInfo.Assembly.DefinedTypes
|
||||
.Where( x => x.IsAbstract == false )
|
||||
.Where( x => x.IsInterface == false )
|
||||
.Where( typeInfo.IsAssignableFrom )
|
||||
.ToArray();
|
||||
|
||||
if( implementingTypes.Length == 0 )
|
||||
{
|
||||
throw new NotSupportedException( $"{typeInfo.Name} must have a concrete implementation." );
|
||||
}
|
||||
|
||||
if( implementingTypes.Length != 1 )
|
||||
{
|
||||
throw new NotSupportedException( $"Requested type: {typeInfo.Name}. " +
|
||||
"Multiple implementations per request interface is not supported." );
|
||||
}
|
||||
|
||||
return implementingTypes[0].DeclaredConstructors.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace DM.MovieApi.Shims
|
||||
{
|
||||
public static class CollectionExtensions
|
||||
{
|
||||
public static IReadOnlyList<T> AsReadOnly<T>( this List<T> list )
|
||||
{
|
||||
return new ReadOnlyCollection<T>( list );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace DM.MovieApi.Shims
|
||||
{
|
||||
[AttributeUsage( AttributeTargets.Constructor )]
|
||||
internal sealed class ImportingConstructorAttribute : Attribute
|
||||
{ }
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
This code is written by nCubed and was taken from
|
||||
https://github.com/nCubed/TheMovieDbWrapper/
|
||||
On November 22nd 2021
|
||||
|
||||
No changes have been done to the code, its just easier to include the code in this DLL instead of having to load it separately.
|
||||
|
||||
See the license for this for more information.
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"runtimeTarget": {
|
||||
"name": ".NETCoreApp,Version=v6.0",
|
||||
"signature": ""
|
||||
},
|
||||
"compilationOptions": {},
|
||||
"targets": {
|
||||
".NETCoreApp,Version=v6.0": {
|
||||
"Plugin/1.0.0": {
|
||||
"runtime": {
|
||||
"Plugin.dll": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"Plugin/1.0.0": {
|
||||
"type": "project",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
Binary file not shown.
+7
-2
@@ -1,12 +1,17 @@
|
||||
[
|
||||
{
|
||||
"Name": "BasicNodes",
|
||||
"Version": "0.0.1.8",
|
||||
"Version": "0.0.1.9",
|
||||
"Package": "https://github.com/revenz/FileFlowsPlugins/blob/master/Builds/BasicNodes.zip?raw=true"
|
||||
},
|
||||
{
|
||||
"Name": "MetaNodes",
|
||||
"Version": "0.0.1.9",
|
||||
"Package": "https://github.com/revenz/FileFlowsPlugins/blob/master/Builds/MetaNodes.zip?raw=true"
|
||||
},
|
||||
{
|
||||
"Name": "VideoNodes",
|
||||
"Version": "0.0.1.8",
|
||||
"Version": "0.0.1.9",
|
||||
"Package": "https://github.com/revenz/FileFlowsPlugins/blob/master/Builds/VideoNodes.zip?raw=true"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user