added plex plugin

This commit is contained in:
John Andrews
2022-04-21 11:36:21 +12:00
parent 342c53c3ba
commit 60ec00e2f5
33 changed files with 319 additions and 1 deletions

1
.gitignore vendored
View File

@@ -12,3 +12,4 @@ deploy/**
*.ffplugin
DiscordNodes/settings.json
Gotify/settings.json
Plex/settings.json

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

6
Plex/ExtensionMethods.cs Normal file
View File

@@ -0,0 +1,6 @@
namespace FileFlows.Plex;
internal static class ExtensionMethods
{
public static string? EmptyAsNull(this string str) => str == string.Empty ? null : str;
}

5
Plex/GlobalUsings.cs Normal file
View File

@@ -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;

View File

@@ -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<PluginSettings>();
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<PlexSections>(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<HttpClient, string, (bool success, string body)> _GetWebRequest;
internal Func<HttpClient, string, (bool success, string body)> 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;
}
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}

BIN
Plex/Plex.csproj Normal file

Binary file not shown.

25
Plex/Plex.en.json Normal file
View File

@@ -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."
}
}
}
}

11
Plex/Plugin.cs Normal file
View File

@@ -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()
{
}
}

18
Plex/PluginSettings.cs Normal file
View File

@@ -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; }
}
}

37
Plex/Tests/PlexTests.cs Normal file
View File

@@ -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

59
Plex/Tests/TestLogger.cs Normal file
View File

@@ -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<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);
}
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

Binary file not shown.

View File

@@ -76,4 +76,10 @@ webhooks
[webhooktoken]
Gotify
gotify
lan
lan
Plex
Jellyfin
Emby
revenz
Fenrus
Plex-Token