diff --git a/.gitignore b/.gitignore index 1f64c8ff..314dadb4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ deploy/** *.ffplugin DiscordNodes/settings.json Gotify/settings.json +Plex/settings.json diff --git a/BasicNodes/BasicNodes.csproj b/BasicNodes/BasicNodes.csproj index ae1951e1..5cf886a1 100644 Binary files a/BasicNodes/BasicNodes.csproj and b/BasicNodes/BasicNodes.csproj differ diff --git a/ChecksumNodes/ChecksumNodes.csproj b/ChecksumNodes/ChecksumNodes.csproj index a2d243b6..142ceff7 100644 Binary files a/ChecksumNodes/ChecksumNodes.csproj and b/ChecksumNodes/ChecksumNodes.csproj differ diff --git a/CollectionNodes/CollectionNodes.csproj b/CollectionNodes/CollectionNodes.csproj index 77d84a9b..2be4de49 100644 Binary files a/CollectionNodes/CollectionNodes.csproj and b/CollectionNodes/CollectionNodes.csproj differ diff --git a/DiscordNodes/DiscordNodes.csproj b/DiscordNodes/DiscordNodes.csproj index f94e691b..6ec81b05 100644 Binary files a/DiscordNodes/DiscordNodes.csproj and b/DiscordNodes/DiscordNodes.csproj differ diff --git a/EmailNodes/EmailNodes.csproj b/EmailNodes/EmailNodes.csproj index 70739188..f21af8b7 100644 Binary files a/EmailNodes/EmailNodes.csproj and b/EmailNodes/EmailNodes.csproj differ diff --git a/FileFlows.Plugin.dll b/FileFlows.Plugin.dll index 721ef00f..46010e0d 100644 Binary files a/FileFlows.Plugin.dll and b/FileFlows.Plugin.dll differ diff --git a/FileFlows.Plugin.pdb b/FileFlows.Plugin.pdb index 592cef91..b06e1bd5 100644 Binary files a/FileFlows.Plugin.pdb and b/FileFlows.Plugin.pdb differ diff --git a/FileFlowsPlugins.sln b/FileFlowsPlugins.sln index 207633fe..19a43ef0 100644 --- a/FileFlowsPlugins.sln +++ b/FileFlowsPlugins.sln @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gotify", "Gotify\Gotify.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordNodes", "DiscordNodes\DiscordNodes.csproj", "{27721E6D-1FBF-4950-824E-5231CACDE64C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plex", "Plex\Plex.csproj", "{4127A3CC-8BCE-41AE-BDE4-6FA75EF2D8A1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +65,10 @@ Global {27721E6D-1FBF-4950-824E-5231CACDE64C}.Debug|Any CPU.Build.0 = Debug|Any CPU {27721E6D-1FBF-4950-824E-5231CACDE64C}.Release|Any CPU.ActiveCfg = Release|Any CPU {27721E6D-1FBF-4950-824E-5231CACDE64C}.Release|Any CPU.Build.0 = Release|Any CPU + {4127A3CC-8BCE-41AE-BDE4-6FA75EF2D8A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4127A3CC-8BCE-41AE-BDE4-6FA75EF2D8A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4127A3CC-8BCE-41AE-BDE4-6FA75EF2D8A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4127A3CC-8BCE-41AE-BDE4-6FA75EF2D8A1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Gotify/Gotify.csproj b/Gotify/Gotify.csproj index a2cee444..3c94bea9 100644 Binary files a/Gotify/Gotify.csproj and b/Gotify/Gotify.csproj differ diff --git a/MetaNodes/MetaNodes.csproj b/MetaNodes/MetaNodes.csproj index e660daa2..c543ae60 100644 Binary files a/MetaNodes/MetaNodes.csproj and b/MetaNodes/MetaNodes.csproj differ diff --git a/MusicNodes/MusicNodes.csproj b/MusicNodes/MusicNodes.csproj index 213d82cc..eb88c39c 100644 Binary files a/MusicNodes/MusicNodes.csproj and b/MusicNodes/MusicNodes.csproj differ diff --git a/Plex/ExtensionMethods.cs b/Plex/ExtensionMethods.cs new file mode 100644 index 00000000..02521988 --- /dev/null +++ b/Plex/ExtensionMethods.cs @@ -0,0 +1,6 @@ +namespace FileFlows.Plex; + +internal static class ExtensionMethods +{ + public static string? EmptyAsNull(this string str) => str == string.Empty ? null : str; +} diff --git a/Plex/GlobalUsings.cs b/Plex/GlobalUsings.cs new file mode 100644 index 00000000..9c301eba --- /dev/null +++ b/Plex/GlobalUsings.cs @@ -0,0 +1,5 @@ +global using System; +global using System.Text; +global using System.ComponentModel.DataAnnotations; +global using FileFlows.Plugin; +global using FileFlows.Plugin.Attributes; \ No newline at end of file diff --git a/Plex/MediaManagement/PlexUpdater.cs b/Plex/MediaManagement/PlexUpdater.cs new file mode 100644 index 00000000..fa337294 --- /dev/null +++ b/Plex/MediaManagement/PlexUpdater.cs @@ -0,0 +1,119 @@ +using FileFlows.Plex.Models; +using System.Text.RegularExpressions; + +namespace FileFlows.Plex.MediaManagement; + +public class PlexUpdater: Node +{ + public override int Inputs => 1; + public override int Outputs => 2; + public override FlowElementType Type => FlowElementType.Process; + public override string Icon => "fas fa-paper-plane"; + + public override int Execute(NodeParameters args) + { + var settings = args.GetPluginSettings(); + + if (string.IsNullOrWhiteSpace(settings?.AccessToken)) + { + args.Logger?.WLog("No access token set"); + return 2; + } + if (string.IsNullOrWhiteSpace(settings?.ServerUrl)) + { + args.Logger?.WLog("No server URL set"); + return 2; + } + + // get the path + string path = args.WorkingFile; + path = args.UnMapPath(path); + if (args.IsDirectory == false) + { + bool windows = path.StartsWith("\\") || Regex.IsMatch(path, @"^[a-zA-Z]:\\"); + string pathSeparator = windows ? "\\" : "/"; + path = path.Substring(0, path.LastIndexOf(pathSeparator)); + } + + string url = settings.ServerUrl; + if (url.EndsWith("/") == false) + url += "/"; + url += "library/sections"; + + using var httpClient = new HttpClient(); + + var sectionsResponse= GetWebRequest(httpClient, url + "?X-Plex-Token=" + settings.AccessToken); + if (sectionsResponse.success == false) + { + args.Logger?.WLog("Failed to retrieve sections" + (string.IsNullOrWhiteSpace(sectionsResponse.body) ? "" : ": " + sectionsResponse.body)); + return 2; + } + + PlexSections sections; + try + { + var options = new System.Text.Json.JsonSerializerOptions(); + options.PropertyNameCaseInsensitive = true; + sections = System.Text.Json.JsonSerializer.Deserialize(sectionsResponse.body, options); + } + catch (Exception ex) + { + args.Logger?.ELog("Failed deserializing sections json: " + ex.Message); + return 2; + } + string pathLower = path.ToLower(); + var section = sections?.MediaContainer?.Directory?.Where(x => { + if (x.Location?.Any() != true) + return false; + foreach (var loc in x.Location) { + if (loc.Path == null) + continue; + if (pathLower.StartsWith(loc.Path.ToLower())) + return true; + } + return false; + }).FirstOrDefault(); + if(section == null) + { + args.Logger?.WLog("Failed to find Plex section for path: " + path); + return 2; + } + + url += $"/{section.Key}/refresh?path={Uri.EscapeDataString(path)}&X-Plex-Token=" + settings.AccessToken; + + var updateResponse = GetWebRequest(httpClient, url); + if (updateResponse.success == false) + { + if(string.IsNullOrWhiteSpace(updateResponse.body) == false) + args.Logger.WLog("Failed to update Plex:" + updateResponse.body); + return 2; + } + return 1; + } + + private Func _GetWebRequest; + internal Func GetWebRequest + { + get + { + if(_GetWebRequest == null) + { + _GetWebRequest = (HttpClient client, string url) => + { + try + { + client.DefaultRequestHeaders.Add("Accept", "application/json"); + var response = client.GetAsync(url).Result; + string body = response.Content.ReadAsStringAsync().Result; + return (response.IsSuccessStatusCode, body); + } + catch(Exception ex) + { + return (false, ex.Message); + } + }; + } + return _GetWebRequest; + } + } +} diff --git a/Plex/Models/PlexDirectory.cs b/Plex/Models/PlexDirectory.cs new file mode 100644 index 00000000..6828394b --- /dev/null +++ b/Plex/Models/PlexDirectory.cs @@ -0,0 +1,13 @@ +namespace FileFlows.Plex.Models; + +internal class PlexDirectory +{ + public string Key { get; set; } + public PlexDirectoryLocation[] Location { get; set; } +} + +public class PlexDirectoryLocation +{ + public int Id { get; set; } + public string Path { get; set; } +} \ No newline at end of file diff --git a/Plex/Models/PlexSections.cs b/Plex/Models/PlexSections.cs new file mode 100644 index 00000000..bdd7b5cd --- /dev/null +++ b/Plex/Models/PlexSections.cs @@ -0,0 +1,12 @@ +namespace FileFlows.Plex.Models; + +internal class PlexSections +{ + public PlexSection MediaContainer { get; set; } +} + +internal class PlexSection +{ + public int Size { get; set; } + public PlexDirectory[] Directory { get; set; } +} diff --git a/Plex/Plex.csproj b/Plex/Plex.csproj new file mode 100644 index 00000000..576dd760 Binary files /dev/null and b/Plex/Plex.csproj differ diff --git a/Plex/Plex.en.json b/Plex/Plex.en.json new file mode 100644 index 00000000..76bd757a --- /dev/null +++ b/Plex/Plex.en.json @@ -0,0 +1,25 @@ +{ + "Plugins": { + "Plex": { + "Description": "A plugin that allows you to communicate with Plex.", + "Fields": { + "ServerUrl": "Server", + "ServerUrl-Placeholder": "http://localhost:32400/", + "ServerUrl-Help": "The URL of the Plex server", + "AccessToken": "Access Token", + "AccessToken-Help": "The access token used to communicate with the Plex server.\nhttps://github.com/revenz/Fenrus/wiki/Plex-Token" + } + } + }, + "Flow": { + "Parts": { + "PlexUpdater": { + "Outputs": { + "1": "Plex update request sent", + "2": "Plex update request failed to send" + }, + "Description": "Sends a message to a Plex server to update the library." + } + } + } +} \ No newline at end of file diff --git a/Plex/Plugin.cs b/Plex/Plugin.cs new file mode 100644 index 00000000..400c7676 --- /dev/null +++ b/Plex/Plugin.cs @@ -0,0 +1,11 @@ +namespace FileFlows.Plex; + +public class Plugin : FileFlows.Plugin.IPlugin +{ + public string Name => "Plex"; + public string MinimumVersion => "0.5.2.690"; + + public void Init() + { + } +} diff --git a/Plex/PluginSettings.cs b/Plex/PluginSettings.cs new file mode 100644 index 00000000..656f7939 --- /dev/null +++ b/Plex/PluginSettings.cs @@ -0,0 +1,18 @@ +namespace FileFlows.Plex +{ + using FileFlows.Plugin; + using FileFlows.Plugin.Attributes; + using System; + using System.ComponentModel.DataAnnotations; + + public class PluginSettings:IPluginSettings + { + [Text(1)] + [Required] + public string ServerUrl { get; set; } + + [Text(2)] + [Required] + public string AccessToken { get; set; } + } +} diff --git a/Plex/Tests/PlexTests.cs b/Plex/Tests/PlexTests.cs new file mode 100644 index 00000000..c719a7e0 --- /dev/null +++ b/Plex/Tests/PlexTests.cs @@ -0,0 +1,37 @@ +#if(DEBUG) + +using FileFlows.Plex.Media; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace FileFlows.Plex.Tests; + +[TestClass] +public class PlexTests +{ + [TestMethod] + public void Plex_Basic() + { + var args = new NodeParameters(@"/media/movies/The Batman (2022)/The Batman.mkv", new TestLogger(), false, string.Empty); + args.GetPluginSettingsJson = (string input) => + { + return File.ReadAllText("../../../settings.json"); + }; + + var node = new PlexUpdater(); + Assert.AreEqual(1, node.Execute(args)); + } + [TestMethod] + public void Plex_Fail() + { + var args = new NodeParameters(@"/media/unknownmovies/The Batman (2022)/The Batman.mkv", new TestLogger(), false, string.Empty); + args.GetPluginSettingsJson = (string input) => + { + return File.ReadAllText("../../../settings.json"); + }; + + var node = new PlexUpdater(); + Assert.AreEqual(2, node.Execute(args)); + } +} + +#endif \ No newline at end of file diff --git a/Plex/Tests/TestLogger.cs b/Plex/Tests/TestLogger.cs new file mode 100644 index 00000000..ce62db67 --- /dev/null +++ b/Plex/Tests/TestLogger.cs @@ -0,0 +1,59 @@ +#if(DEBUG) + +namespace FileFlows.Plex.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 Messages = new List(); + + 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); + } + + public override string ToString() + { + return String.Join(Environment.NewLine, this.Messages.ToArray()); + } + + public string GetTail(int length = 50) + { + if (length <= 0) + length = 50; + if (Messages.Count <= length) + return string.Join(Environment.NewLine, Messages); + return string.Join(Environment.NewLine, Messages.TakeLast(length)); + } +} + +#endif \ No newline at end of file diff --git a/VideoNodes/VideoNodes.csproj b/VideoNodes/VideoNodes.csproj index ef2f9e17..90f00b73 100644 Binary files a/VideoNodes/VideoNodes.csproj and b/VideoNodes/VideoNodes.csproj differ diff --git a/build/utils/PluginInfoGenerator/FileFlows.Plugin.dll b/build/utils/PluginInfoGenerator/FileFlows.Plugin.dll index 2e9d64b1..5c4fa406 100644 Binary files a/build/utils/PluginInfoGenerator/FileFlows.Plugin.dll and b/build/utils/PluginInfoGenerator/FileFlows.Plugin.dll differ diff --git a/build/utils/PluginInfoGenerator/FileFlows.Plugin.pdb b/build/utils/PluginInfoGenerator/FileFlows.Plugin.pdb index 49adcb8f..85aa6688 100644 Binary files a/build/utils/PluginInfoGenerator/FileFlows.Plugin.pdb and b/build/utils/PluginInfoGenerator/FileFlows.Plugin.pdb differ diff --git a/build/utils/PluginInfoGenerator/FileFlows.ServerShared.dll b/build/utils/PluginInfoGenerator/FileFlows.ServerShared.dll index 852cd5df..15fa6007 100644 Binary files a/build/utils/PluginInfoGenerator/FileFlows.ServerShared.dll and b/build/utils/PluginInfoGenerator/FileFlows.ServerShared.dll differ diff --git a/build/utils/PluginInfoGenerator/FileFlows.ServerShared.pdb b/build/utils/PluginInfoGenerator/FileFlows.ServerShared.pdb index 4ff3c1be..5ae8c99f 100644 Binary files a/build/utils/PluginInfoGenerator/FileFlows.ServerShared.pdb and b/build/utils/PluginInfoGenerator/FileFlows.ServerShared.pdb differ diff --git a/build/utils/PluginInfoGenerator/FileFlows.Shared.dll b/build/utils/PluginInfoGenerator/FileFlows.Shared.dll index b7cc3dfb..64ceead1 100644 Binary files a/build/utils/PluginInfoGenerator/FileFlows.Shared.dll and b/build/utils/PluginInfoGenerator/FileFlows.Shared.dll differ diff --git a/build/utils/PluginInfoGenerator/FileFlows.Shared.pdb b/build/utils/PluginInfoGenerator/FileFlows.Shared.pdb index 29f3d3a7..335a2d44 100644 Binary files a/build/utils/PluginInfoGenerator/FileFlows.Shared.pdb and b/build/utils/PluginInfoGenerator/FileFlows.Shared.pdb differ diff --git a/build/utils/PluginInfoGenerator/PluginInfoGenerator.dll b/build/utils/PluginInfoGenerator/PluginInfoGenerator.dll index a7cf1851..6eea1654 100644 Binary files a/build/utils/PluginInfoGenerator/PluginInfoGenerator.dll and b/build/utils/PluginInfoGenerator/PluginInfoGenerator.dll differ diff --git a/build/utils/PluginInfoGenerator/PluginInfoGenerator.pdb b/build/utils/PluginInfoGenerator/PluginInfoGenerator.pdb index 8d76dfef..6bddf1bd 100644 Binary files a/build/utils/PluginInfoGenerator/PluginInfoGenerator.pdb and b/build/utils/PluginInfoGenerator/PluginInfoGenerator.pdb differ diff --git a/build/utils/spellcheck/ignoredwords.txt b/build/utils/spellcheck/ignoredwords.txt index b9a56c2e..c92975c0 100644 --- a/build/utils/spellcheck/ignoredwords.txt +++ b/build/utils/spellcheck/ignoredwords.txt @@ -76,4 +76,10 @@ webhooks [webhooktoken] Gotify gotify -lan \ No newline at end of file +lan +Plex +Jellyfin +Emby +revenz +Fenrus +Plex-Token