chore:file cleanup

This commit is contained in:
2025-09-12 15:41:56 -04:00
parent 4ef28c3021
commit 558a786bdb
90 changed files with 136 additions and 5265 deletions
+11
View File
@@ -0,0 +1,11 @@
# Files Removed During Repository Cleanup
## Removed Files:
- `example-docker-compose.yml` - Replaced with properly named `docker-compose.example.yml`
- `example-multi-registry-workflow.yml` - Personal CI/CD workflow not needed for public repo
## Reason:
These were development/CI artifacts that don't belong in a clean public webhook application repository.
## Result:
Clean, focused NFOGuard webhook Docker application repository ready for public use.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+18
View File
@@ -0,0 +1,18 @@
# Files to Remove from NFOGuard Webhook Repository
# These are CI/development artifacts that don't belong in the public webhook app repo
gitea-registry-troubleshooting.md
gitea-packages-config.ini
find-gitea-local-ip.sh
example-multi-registry-workflow.yml
example-docker-compose.yml
example-distribution-README.md
docker-compose.example.yml
cleanup-github.sh
check-gitea-registry.sh
cleanup-files.sh
# You can remove them with:
# git rm <filename>
# or
# rm <filename> && git add -u
@@ -1,91 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>NFOGuard Settings</title>
</head>
<body data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox">
<div data-role="content">
<div class="content-primary">
<form id="nfoguardConfigForm">
<h1>NFOGuard Settings</h1>
<p>Configure how NFOGuard synchronizes TV episode dates in Emby.</p>
<div class="inputContainer">
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" id="chkEnableRealTimeSync" />
<span>Enable Real-time Sync</span>
</label>
<div class="fieldDescription">Automatically sync PremiereDate to DateCreated when episodes are added or updated</div>
</div>
<div class="inputContainer">
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" id="chkEnableScheduledTask" />
<span>Enable Scheduled Task</span>
</label>
<div class="fieldDescription">Run NFOGuard as a scheduled task to process existing episodes</div>
</div>
<div class="inputContainer">
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" id="chkLogVerbose" />
<span>Verbose Logging</span>
</label>
<div class="fieldDescription">Enable detailed logging for troubleshooting</div>
</div>
<br/>
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
Save
</button>
</div>
</form>
</div>
</div>
<script type="text/javascript">
(function () {
var pluginId = "B8A7F9E2-1234-4567-8901-2B3C4D5E6F7A";
function loadPage() {
ApiClient.getPluginConfiguration(pluginId).then(function (config) {
document.getElementById('chkEnableRealTimeSync').checked = config.EnableRealTimeSync !== false;
document.getElementById('chkEnableScheduledTask').checked = config.EnableScheduledTask === true;
document.getElementById('chkLogVerbose').checked = config.LogVerbose === true;
}).catch(function(err) {
console.error('Error loading NFOGuard config:', err);
});
}
function saveConfig() {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(pluginId).then(function (config) {
config.EnableRealTimeSync = document.getElementById('chkEnableRealTimeSync').checked;
config.EnableScheduledTask = document.getElementById('chkEnableScheduledTask').checked;
config.LogVerbose = document.getElementById('chkLogVerbose').checked;
return ApiClient.updatePluginConfiguration(pluginId, config);
}).then(function () {
Dashboard.hideLoadingMsg();
Dashboard.alert('Settings saved successfully.');
}).catch(function (err) {
Dashboard.hideLoadingMsg();
Dashboard.alert('Error saving settings: ' + (err.message || err));
console.error('Save error:', err);
});
}
document.getElementById('nfoguardConfigForm').addEventListener('submit', function (e) {
e.preventDefault();
saveConfig();
return false;
});
// Load settings when page loads
loadPage();
})();
</script>
</body>
</html>
-70
View File
@@ -1,70 +0,0 @@
# NFOGuard Emby Plugin Deployment Guide
## Prerequisites
- .NET Framework 4.8 SDK or Visual Studio Build Tools
- Emby Server 4.7+ running
- Access to Emby's plugins directory
## Building the Plugin
### Windows
```cmd
build.bat
```
### Linux/macOS
```bash
./build.sh
```
## Installation Steps
1. **Build the plugin** using the build script above
2. **Locate your Emby plugins directory:**
- Windows: `C:\ProgramData\Emby-Server\plugins`
- Linux: `/var/lib/emby/plugins`
- Docker: `/config/plugins` (inside container)
- Synology: `/volume1/Emby/plugins`
3. **Copy the DLL:**
```bash
cp bin/Release/NFOGuard.Emby.Plugin.dll /path/to/emby/plugins/
```
4. **Restart Emby Server**
5. **Verify installation:**
- Check Emby dashboard → Plugins
- Look for "NFOGuard" in the installed plugins list
- Check Emby logs for "NFOGuard :: Plugin starting up"
## Testing
1. **Add a new TV episode** to your library with an NFO file containing `<dateadded>`
2. **Check the logs** for NFOGuard processing messages:
```
NFOGuard :: Processing Added Episode :: Episode Name
NFOGuard :: Syncing PremiereDate to DateCreated :: Episode Name
NFOGuard :: Successfully updated :: Episode Name
```
3. **Verify in database:**
```sql
sqlite3 library.db2 "SELECT DateCreated, PremiereDate FROM MediaItems WHERE Name LIKE '%episode%';"
```
## Troubleshooting
- **Plugin not appearing:** Check Emby logs for loading errors
- **Not processing episodes:** Verify log level includes Info messages
- **Build failures:** Ensure all Emby SDK DLLs are in EmbySDK folder
- **Permission errors:** Ensure Emby service has write access to database
## SDK Dependencies
The plugin requires these Emby SDK DLLs in the `EmbySDK` folder:
- MediaBrowser.Common.dll
- MediaBrowser.Controller.dll
- MediaBrowser.Model.dll
These can be extracted from your Emby Server installation directory.
-173
View File
@@ -1,173 +0,0 @@
using System;
using System.Linq;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
namespace NFOGuard.Emby.Plugin
{
public class FreeTierManager
{
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
public FreeTierManager(ILogger logger, ILibraryManager libraryManager = null)
{
_logger = logger;
_libraryManager = libraryManager;
}
public bool CanProcessItems(int itemCount, out string reason)
{
var config = Plugin.Instance.Configuration;
reason = string.Empty;
// If licensed or no limit set, allow processing
if (config.LicenseValid || config.MonthlyProcessingLimit == 0)
{
return true;
}
// Check if month has rolled over
ResetMonthlyCounterIfNeeded();
// Check if this processing would exceed the limit
var wouldExceed = config.ProcessedThisMonth + itemCount > config.MonthlyProcessingLimit;
if (wouldExceed)
{
var remaining = Math.Max(0, config.MonthlyProcessingLimit - config.ProcessedThisMonth);
reason = $"Free tier limit reached. {remaining} items remaining this month of {config.MonthlyProcessingLimit} allowed. Upgrade to process unlimited items.";
_logger.Info($"NFOGuard :: {reason}");
return false;
}
return true;
}
public void RecordProcessedItems(int count)
{
var config = Plugin.Instance.Configuration;
// Only track if using free tier limits
if (config.MonthlyProcessingLimit > 0 && !config.LicenseValid)
{
config.ProcessedThisMonth += count;
Plugin.Instance.SaveConfiguration();
_logger.Info($"NFOGuard :: Free tier usage: {config.ProcessedThisMonth}/{config.MonthlyProcessingLimit} items this month");
}
}
public void SetFreeTierLimits(int monthlyLimit)
{
var config = Plugin.Instance.Configuration;
config.MonthlyProcessingLimit = monthlyLimit;
config.LicenseValid = false;
if (config.InstallDate == DateTime.MinValue)
{
config.InstallDate = DateTime.UtcNow;
}
ResetMonthlyCounterIfNeeded();
Plugin.Instance.SaveConfiguration();
_logger.Info($"NFOGuard :: Free tier configured: {monthlyLimit} items per month");
}
public void SetPercentageBasedLimits()
{
if (_libraryManager == null)
{
_logger.Warn("NFOGuard :: Cannot calculate percentage limits without LibraryManager");
SetFreeTierLimits(500); // Fallback to fixed limit
return;
}
try
{
// Get library counts
var episodes = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { "Episode" },
Recursive = true
}).OfType<Episode>().Count();
var movies = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { "Movie" },
Recursive = true
}).OfType<Movie>().Count();
// Calculate 2% of TV episodes + 5% of movies
var episodeLimit = Math.Max(1, (int)(episodes * 0.02)); // At least 1 episode
var movieLimit = Math.Max(1, (int)(movies * 0.05)); // At least 1 movie
var totalLimit = episodeLimit + movieLimit;
_logger.Info($"NFOGuard :: Library analysis - Episodes: {episodes}, Movies: {movies}");
_logger.Info($"NFOGuard :: Free tier limits - Episodes: {episodeLimit} (2%), Movies: {movieLimit} (5%), Total: {totalLimit}");
var config = Plugin.Instance.Configuration;
config.MonthlyProcessingLimit = totalLimit;
config.LicenseValid = false;
if (config.InstallDate == DateTime.MinValue)
{
config.InstallDate = DateTime.UtcNow;
}
ResetMonthlyCounterIfNeeded();
Plugin.Instance.SaveConfiguration();
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: Error calculating percentage limits", ex);
SetFreeTierLimits(500); // Fallback
}
}
public void EnableFullVersion()
{
var config = Plugin.Instance.Configuration;
config.LicenseValid = true;
config.MonthlyProcessingLimit = 0;
Plugin.Instance.SaveConfiguration();
_logger.Info("NFOGuard :: Full version enabled - unlimited processing");
}
private void ResetMonthlyCounterIfNeeded()
{
var config = Plugin.Instance.Configuration;
var now = DateTime.UtcNow;
// Reset if last reset was more than a month ago or never set
if (config.LastProcessingReset == DateTime.MinValue ||
now > config.LastProcessingReset.AddMonths(1))
{
config.ProcessedThisMonth = 0;
config.LastProcessingReset = new DateTime(now.Year, now.Month, 1); // First of current month
Plugin.Instance.SaveConfiguration();
_logger.Info("NFOGuard :: Monthly processing counter reset");
}
}
public string GetUsageInfo()
{
var config = Plugin.Instance.Configuration;
if (config.LicenseValid || config.MonthlyProcessingLimit == 0)
{
return "Full version - unlimited processing";
}
ResetMonthlyCounterIfNeeded();
var remaining = Math.Max(0, config.MonthlyProcessingLimit - config.ProcessedThisMonth);
return $"Free tier: {remaining} items remaining this month (of {config.MonthlyProcessingLimit} allowed)";
}
}
}
-169
View File
@@ -1,169 +0,0 @@
using System;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Logging;
namespace NFOGuard.Emby.Plugin
{
public class LibraryEventHandler : IDisposable
{
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepository;
private readonly ILogger _logger;
private readonly LicenseManager _licenseManager;
private readonly FreeTierManager _freeTierManager;
public LibraryEventHandler(ILibraryManager libraryManager, IItemRepository itemRepository, ILogger logger)
{
_libraryManager = libraryManager;
_itemRepository = itemRepository;
_logger = logger;
_licenseManager = new LicenseManager(_logger);
_freeTierManager = new FreeTierManager(_logger);
// Subscribe to library events
_libraryManager.ItemAdded += OnItemAdded;
_libraryManager.ItemUpdated += OnItemUpdated;
}
private void OnItemAdded(object sender, ItemChangeEventArgs e)
{
ProcessItem(e.Item, "Added");
}
private void OnItemUpdated(object sender, ItemChangeEventArgs e)
{
ProcessItem(e.Item, "Updated");
}
private void ProcessItem(BaseItem item, string eventType)
{
try
{
// Check if real-time sync is enabled
if (!Plugin.Instance.Configuration.EnableRealTimeSync)
return;
// Free tier: disable real-time processing
var config = Plugin.Instance.Configuration;
if (config.MonthlyProcessingLimit > 0 && !config.LicenseValid)
{
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Real-time processing disabled in free tier. Use scheduled task instead.");
return;
}
// Check if we can process this item
if (!_freeTierManager.CanProcessItems(1, out string reason))
{
_logger.Info($"NFOGuard :: Skipping real-time processing: {reason}");
return;
}
// Process TV episodes and Movies
if (item is Episode episode)
{
ProcessEpisode(episode, eventType);
}
else if (item is Movie movie)
{
ProcessMovie(movie, eventType);
}
}
catch (Exception ex)
{
_logger.ErrorException($"NFOGuard :: Error processing item {item?.Name}", ex);
}
}
private void ProcessEpisode(Episode episode, string eventType)
{
try
{
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Processing {eventType} Episode :: {episode.Name}");
// Check if PremiereDate exists and differs from DateCreated
if (episode.PremiereDate.HasValue &&
episode.DateCreated != episode.PremiereDate.Value)
{
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Syncing PremiereDate to DateCreated :: {episode.Name} :: " +
$"PremiereDate: {episode.PremiereDate.Value}, DateCreated: {episode.DateCreated}");
// Update DateCreated to match PremiereDate
episode.DateCreated = episode.PremiereDate.Value;
try
{
// Save to repository using ItemUpdateType.MetadataEdit
episode.UpdateToRepository(ItemUpdateType.MetadataEdit);
_logger.Info($"NFOGuard :: Successfully updated episode :: {episode.Name}");
// Record usage for free tier tracking
_freeTierManager.RecordProcessedItems(1);
}
catch (Exception ex)
{
_logger.ErrorException($"NFOGuard :: Failed to update episode {episode.Name}", ex);
}
}
}
catch (Exception ex)
{
_logger.ErrorException($"NFOGuard :: Error processing episode {episode?.Name}", ex);
}
}
private void ProcessMovie(Movie movie, string eventType)
{
try
{
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Processing {eventType} Movie :: {movie.Name}");
// Check if PremiereDate exists and differs from DateCreated
if (movie.PremiereDate.HasValue &&
movie.DateCreated != movie.PremiereDate.Value)
{
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Syncing PremiereDate to DateCreated :: {movie.Name} :: " +
$"PremiereDate: {movie.PremiereDate.Value}, DateCreated: {movie.DateCreated}");
// Update DateCreated to match PremiereDate
movie.DateCreated = movie.PremiereDate.Value;
try
{
// Save to repository using ItemUpdateType.MetadataEdit
movie.UpdateToRepository(ItemUpdateType.MetadataEdit);
_logger.Info($"NFOGuard :: Successfully updated movie :: {movie.Name}");
// Record usage for free tier tracking
_freeTierManager.RecordProcessedItems(1);
}
catch (Exception ex)
{
_logger.ErrorException($"NFOGuard :: Failed to update movie {movie.Name}", ex);
}
}
}
catch (Exception ex)
{
_logger.ErrorException($"NFOGuard :: Error processing movie {movie?.Name}", ex);
}
}
public void Dispose()
{
if (_libraryManager != null)
{
_libraryManager.ItemAdded -= OnItemAdded;
_libraryManager.ItemUpdated -= OnItemUpdated;
}
}
}
}
-274
View File
@@ -1,274 +0,0 @@
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
namespace NFOGuard.Emby.Plugin
{
public class LicenseManager
{
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
private const string NFOGUARD_API_URL = "https://nfoguard.com/api"; // Your future API endpoint
public LicenseManager(ILogger logger)
{
_logger = logger;
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(10); // Quick timeout for license checks
}
public async Task<bool> CheckLicenseStatusAsync()
{
try
{
var config = Plugin.Instance.Configuration;
// Get unique identifier for this installation
var machineId = GetMachineIdentifier();
_logger.Info("NFOGuard :: Checking automatic license status...");
// Check Patreon subscription
var patreonValid = await CheckPatreonSubscriptionAsync(machineId);
if (patreonValid)
{
_logger.Info("NFOGuard :: Valid Patreon subscription found");
return true;
}
// Check PayPal recurring payment
var paypalValid = await CheckPayPalSubscriptionAsync(machineId);
if (paypalValid)
{
_logger.Info("NFOGuard :: Valid PayPal subscription found");
return true;
}
_logger.Info("NFOGuard :: No active subscription found, using free tier");
return false;
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: License validation error", ex);
return false; // Default to free tier on error
}
}
private async Task<bool> CheckPatreonSubscriptionAsync(string machineId)
{
try
{
var requestUrl = $"{NFOGUARD_API_URL}/patreon/check?machine_id={machineId}";
var response = await _httpClient.GetStringAsync(requestUrl);
// Parse response (you'll implement the API to return JSON like {"active": true, "tier": "supporter"})
return response.Contains("\"active\":true");
}
catch (HttpRequestException)
{
_logger.Info("NFOGuard :: Cannot reach license server, running in offline mode");
return false; // Offline mode - use free tier
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: Patreon check error", ex);
return false;
}
}
private async Task<bool> CheckPayPalSubscriptionAsync(string machineId)
{
try
{
var requestUrl = $"{NFOGUARD_API_URL}/paypal/check?machine_id={machineId}";
var response = await _httpClient.GetStringAsync(requestUrl);
// Parse response
return response.Contains("\"active\":true");
}
catch (HttpRequestException)
{
_logger.Info("NFOGuard :: Cannot reach license server, running in offline mode");
return false;
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: PayPal check error", ex);
return false;
}
}
public string GetMachineIdentifier()
{
try
{
// Create a unique but consistent identifier for this installation
// Combine multiple factors to create a stable machine ID
var machineName = Environment.MachineName ?? "unknown";
var userDomain = Environment.UserDomainName ?? "unknown";
var installPath = Plugin.Instance.ConfigurationFilePath;
var combined = $"{machineName}-{userDomain}-{installPath}".GetHashCode().ToString("X8");
_logger.Info($"NFOGuard :: Machine ID: {combined}");
return combined;
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: Error generating machine ID", ex);
return "FALLBACK-ID";
}
}
public bool IsTrialExpired()
{
// Implement trial logic here
// For example, check installation date vs current date
var installDate = Plugin.Instance.Configuration.InstallDate;
if (installDate == DateTime.MinValue)
{
Plugin.Instance.Configuration.InstallDate = DateTime.UtcNow;
Plugin.Instance.SaveConfiguration();
return false;
}
var trialDays = 30; // 30-day trial
return DateTime.UtcNow > installDate.AddDays(trialDays);
}
public bool CanUseFeature()
{
// Check if license is valid or trial is still active
return Plugin.Instance.Configuration.LicenseValid || !IsTrialExpired();
}
public async Task<bool> CheckAndActivateLicenseAsync()
{
try
{
_logger.Info("NFOGuard :: Checking for active subscriptions...");
var isValid = await CheckLicenseStatusAsync();
var config = Plugin.Instance.Configuration;
if (isValid && !config.LicenseValid)
{
// Activate license
config.LicenseValid = true;
config.MonthlyProcessingLimit = 0; // Remove limits
config.ProcessedThisMonth = 0; // Reset counter
Plugin.Instance.SaveConfiguration();
var freeTierManager = new FreeTierManager(_logger);
freeTierManager.EnableFullVersion();
_logger.Info("NFOGuard :: Subscription detected - unlimited processing activated!");
return true;
}
else if (!isValid && config.LicenseValid)
{
// Deactivate license
config.LicenseValid = false;
Plugin.Instance.SaveConfiguration();
var freeTierManager = new FreeTierManager(_logger);
freeTierManager.SetPercentageBasedLimits();
_logger.Info("NFOGuard :: No active subscription found - switched to free tier");
return false;
}
return isValid;
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: License check error", ex);
return false;
}
}
public async Task<string> RegisterMachineAsync(string userEmail)
{
try
{
var machineId = GetMachineIdentifier();
var config = Plugin.Instance.Configuration;
var registrationData = new
{
machine_id = machineId,
email = userEmail,
emby_version = Environment.OSVersion.ToString(),
plugin_version = Plugin.Instance.Description,
install_date = config.InstallDate.ToString("yyyy-MM-dd"),
library_stats = GetLibraryStats()
};
var json = System.Text.Json.JsonSerializer.Serialize(registrationData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"{NFOGUARD_API_URL}/register", content);
var responseText = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
_logger.Info($"NFOGuard :: Machine registered successfully: {machineId}");
return $"Registration successful!\n\nMachine ID: {machineId}\n\nTo activate unlimited processing:\n• Subscribe on Patreon: https://patreon.com/nfoguard\n• Or PayPal recurring: https://nfoguard.com/paypal\n\nThe plugin will automatically detect your subscription within 24 hours.";
}
else
{
_logger.Warn($"NFOGuard :: Registration failed: {responseText}");
return $"Registration failed. You can still use the free tier.\n\nMachine ID: {machineId}\nError: {responseText}";
}
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: Registration error", ex);
var machineId = GetMachineIdentifier();
return $"Registration failed (offline mode).\n\nMachine ID: {machineId}\n\nTo activate later, visit: https://nfoguard.com/register";
}
}
private object GetLibraryStats()
{
try
{
// This would be used for analytics (anonymized)
return new
{
episodes_estimated = "unknown",
movies_estimated = "unknown",
free_tier_usage = Plugin.Instance.Configuration.ProcessedThisMonth
};
}
catch
{
return new { error = "stats_unavailable" };
}
}
public void DeactivateLicense()
{
try
{
var config = Plugin.Instance.Configuration;
config.LicenseValid = false;
// Reset to percentage-based free tier
var freeTierManager = new FreeTierManager(_logger);
freeTierManager.SetPercentageBasedLimits();
Plugin.Instance.SaveConfiguration();
_logger.Info("NFOGuard :: License deactivated - switched to free tier");
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: License deactivation error", ex);
}
}
}
}
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<OutputType>Library</OutputType>
<RootNamespace>NFOGuard.Emby.Plugin</RootNamespace>
<AssemblyName>NFOGuard.Emby.Plugin</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.5" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.Json" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="MediaBrowser.Common">
<HintPath>..\EmbySDK\MediaBrowser.Common.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MediaBrowser.Controller">
<HintPath>..\EmbySDK\MediaBrowser.Controller.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MediaBrowser.Model">
<HintPath>..\EmbySDK\MediaBrowser.Model.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<!-- All web resources disabled to prevent UI loading issues -->
</Project>
@@ -1,226 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Tasks;
namespace NFOGuard.Emby.Plugin
{
public class NFOGuardScheduledTask : IScheduledTask
{
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly FreeTierManager _freeTierManager;
public string Name => "DateSync Task";
public string Key => "NFOGuardSync";
public string Description => "Synchronizes PremiereDate to DateCreated for all TV episodes and movies";
public string Category => "Library";
public NFOGuardScheduledTask(ILibraryManager libraryManager, ILogManager logManager)
{
_libraryManager = libraryManager;
_logger = logManager.GetLogger("NFOGuard");
_freeTierManager = new FreeTierManager(_logger);
}
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
if (!Plugin.Instance.Configuration.EnableScheduledTask)
{
_logger.Info("NFOGuard :: Scheduled task is disabled in configuration");
return;
}
_logger.Info("NFOGuard :: Starting scheduled task execution");
try
{
// Get all TV episodes and movies
var episodes = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { "Episode" },
Recursive = true
}).OfType<Episode>().ToList();
var movies = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { "Movie" },
Recursive = true
}).OfType<Movie>().ToList();
var totalItems = episodes.Count + movies.Count;
_logger.Info($"NFOGuard :: Found {episodes.Count} episodes and {movies.Count} movies to process ({totalItems} total)");
// Calculate items that need processing
var itemsNeedingSync = episodes.Where(e => e.PremiereDate.HasValue && e.DateCreated != e.PremiereDate.Value).Count() +
movies.Where(m => m.PremiereDate.HasValue && m.DateCreated != m.PremiereDate.Value).Count();
_logger.Info($"NFOGuard :: {itemsNeedingSync} items need date synchronization");
// Check free tier limits
if (!_freeTierManager.CanProcessItems(itemsNeedingSync, out string reason))
{
_logger.Info($"NFOGuard :: {reason}");
// In free tier, process only what we can
var pluginConfig = Plugin.Instance.Configuration;
if (pluginConfig.MonthlyProcessingLimit > 0 && !pluginConfig.LicenseValid)
{
var allowedCount = Math.Max(0, pluginConfig.MonthlyProcessingLimit - pluginConfig.ProcessedThisMonth);
_logger.Info($"NFOGuard :: Free tier: Processing {allowedCount} items only");
if (allowedCount == 0)
{
_logger.Info("NFOGuard :: Free tier limit reached. No items will be processed this month.");
return;
}
}
}
var processedCount = 0;
var updatedCount = 0;
var config = Plugin.Instance.Configuration;
var remainingQuota = config.MonthlyProcessingLimit > 0 && !config.LicenseValid
? Math.Max(0, config.MonthlyProcessingLimit - config.ProcessedThisMonth)
: int.MaxValue;
// Process episodes
for (int i = 0; i < episodes.Count && (config.LicenseValid || remainingQuota > 0); i++)
{
cancellationToken.ThrowIfCancellationRequested();
var episode = episodes[i];
try
{
// Always log detailed info to diagnose the issue
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Episode: {episode.Name} :: " +
$"PremiereDate: {episode.PremiereDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? "NULL"}, " +
$"DateCreated: {episode.DateCreated.ToString("yyyy-MM-dd HH:mm:ss")}");
if (episode.PremiereDate.HasValue &&
episode.DateCreated != episode.PremiereDate.Value)
{
_logger.Info($"NFOGuard :: SYNCING episode: {episode.Name} :: " +
$"PremiereDate: {episode.PremiereDate.Value}, DateCreated: {episode.DateCreated}");
episode.DateCreated = episode.PremiereDate.Value;
episode.UpdateToRepository(ItemUpdateType.MetadataEdit);
updatedCount++;
// Track usage and decrement quota
_freeTierManager.RecordProcessedItems(1);
if (!config.LicenseValid && config.MonthlyProcessingLimit > 0)
{
remainingQuota--;
}
}
else if (!episode.PremiereDate.HasValue)
{
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Episode {episode.Name} has no PremiereDate - skipping");
}
else
{
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Episode {episode.Name} dates already match - no sync needed");
}
processedCount++;
var progressPercentage = (double)processedCount / totalItems * 100;
progress?.Report(progressPercentage);
}
catch (Exception ex)
{
_logger.ErrorException($"NFOGuard :: Error processing episode {episode.Name}", ex);
}
}
// Process movies
for (int i = 0; i < movies.Count && (config.LicenseValid || remainingQuota > 0); i++)
{
cancellationToken.ThrowIfCancellationRequested();
var movie = movies[i];
try
{
// Always log detailed info to diagnose the issue
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Movie: {movie.Name} :: " +
$"PremiereDate: {movie.PremiereDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? "NULL"}, " +
$"DateCreated: {movie.DateCreated.ToString("yyyy-MM-dd HH:mm:ss")}");
if (movie.PremiereDate.HasValue &&
movie.DateCreated != movie.PremiereDate.Value)
{
_logger.Info($"NFOGuard :: SYNCING movie: {movie.Name} :: " +
$"PremiereDate: {movie.PremiereDate.Value}, DateCreated: {movie.DateCreated}");
movie.DateCreated = movie.PremiereDate.Value;
movie.UpdateToRepository(ItemUpdateType.MetadataEdit);
updatedCount++;
// Track usage and decrement quota
_freeTierManager.RecordProcessedItems(1);
if (!config.LicenseValid && config.MonthlyProcessingLimit > 0)
{
remainingQuota--;
}
}
else if (!movie.PremiereDate.HasValue)
{
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Movie {movie.Name} has no PremiereDate - skipping");
}
else
{
if (Plugin.Instance.Configuration.LogVerbose)
_logger.Info($"NFOGuard :: Movie {movie.Name} dates already match - no sync needed");
}
processedCount++;
var progressPercentage = (double)processedCount / totalItems * 100;
progress?.Report(progressPercentage);
}
catch (Exception ex)
{
_logger.ErrorException($"NFOGuard :: Error processing movie {movie.Name}", ex);
}
}
_logger.Info($"NFOGuard :: Scheduled task completed. Processed: {processedCount} items ({episodes.Count} episodes, {movies.Count} movies), Updated: {updatedCount}");
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: Scheduled task failed", ex);
throw;
}
}
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
// Default to run weekly at 3 AM on Sunday
return new[]
{
new TaskTriggerInfo
{
Type = TaskTriggerInfo.TriggerWeekly,
DayOfWeek = DayOfWeek.Sunday,
TimeOfDayTicks = TimeSpan.FromHours(3).Ticks
}
};
}
}
}
-139
View File
@@ -1,139 +0,0 @@
using System;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Logging;
namespace NFOGuard.Emby.Plugin
{
public class Plugin : BasePlugin<PluginConfiguration>
{
private Guid _id = new Guid("B2C3D4E5-6789-ABCD-EF01-23456789ABCE"); // Completely new GUID to clear cache
public override string Name => "DateSync";
public override string Description => "Synchronizes TV episode PremiereDate to DateCreated to preserve import dates in Emby";
public override Guid Id => _id;
public override string ConfigurationFileName => "datesync.xml";
public static Plugin Instance { get; private set; }
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
{
Instance = this;
}
/// <summary>
/// Registers machine and checks for automatic subscription activation
/// </summary>
public async Task<string> RegisterAndCheckSubscriptionAsync(string userEmail, ILogger logger)
{
try
{
var licenseManager = new LicenseManager(logger);
// Register machine first
var registrationResult = await licenseManager.RegisterMachineAsync(userEmail);
// Then check for existing subscription
await licenseManager.CheckAndActivateLicenseAsync();
return registrationResult;
}
catch (Exception ex)
{
return $"Registration error: {ex.Message}";
}
}
/// <summary>
/// Gets current license status and usage information
/// </summary>
public string GetLicenseStatus(ILogger logger)
{
try
{
var freeTierManager = new FreeTierManager(logger);
var licenseManager = new LicenseManager(logger);
var machineId = licenseManager.GetMachineIdentifier();
if (Configuration.LicenseValid)
{
return $"✅ Subscription Active\nMachine ID: {machineId}\nStatus: Unlimited processing enabled\n\nSubscription automatically detected via Patreon/PayPal.";
}
else
{
var usageInfo = freeTierManager.GetUsageInfo();
var trialInfo = licenseManager.IsTrialExpired() ? "Trial expired" : $"Trial active ({Math.Max(0, (Configuration.InstallDate.AddDays(30) - DateTime.UtcNow).Days)} days remaining)";
return $"🆓 Free Tier Active\nMachine ID: {machineId}\nUsage: {usageInfo}\nTrial: {trialInfo}\n\nTo unlock unlimited processing:\n• Subscribe: https://patreon.com/nfoguard\n• PayPal: https://nfoguard.com/paypal\n\nActivation is automatic within 24 hours.";
}
}
catch (Exception ex)
{
return $"Error getting license status: {ex.Message}";
}
}
/// <summary>
/// Manually checks for subscription updates
/// </summary>
public async Task<string> RefreshSubscriptionStatusAsync(ILogger logger)
{
try
{
var licenseManager = new LicenseManager(logger);
var wasLicensed = Configuration.LicenseValid;
await licenseManager.CheckAndActivateLicenseAsync();
if (!wasLicensed && Configuration.LicenseValid)
{
return "✅ Subscription found and activated! Unlimited processing enabled.";
}
else if (wasLicensed && !Configuration.LicenseValid)
{
return "⚠️ Subscription expired or cancelled. Switched to free tier.";
}
else if (Configuration.LicenseValid)
{
return "✅ Subscription is still active.";
}
else
{
return "🆓 No active subscription found. Still using free tier.";
}
}
catch (Exception ex)
{
return $"Error checking subscription: {ex.Message}";
}
}
}
public class PluginConfiguration : BasePluginConfiguration
{
public bool EnableRealTimeSync { get; set; } = true;
public bool EnableScheduledTask { get; set; } = true;
public bool LogVerbose { get; set; } = true; // Enabled by default for diagnostic version
public bool LicenseValid { get; set; } = false;
public string RegisteredEmail { get; set; } = string.Empty;
public DateTime InstallDate { get; set; } = DateTime.MinValue;
public DateTime LastLicenseCheck { get; set; } = DateTime.MinValue;
// Free tier tracking
public DateTime LastProcessingReset { get; set; } = DateTime.MinValue;
public int ProcessedThisMonth { get; set; } = 0;
public int MonthlyProcessingLimit { get; set; } = 0; // 0 = no limit (full version)
// Machine registration
public bool MachineRegistered { get; set; } = false;
public string MachineId { get; set; } = string.Empty;
}
}
@@ -1,29 +0,0 @@
using System.IO;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller.Plugins;
namespace NFOGuard.Emby.Plugin
{
public class PluginConfigurationPage : IPluginConfigurationPage
{
public string Name => "NFOGuard";
public ConfigurationPageType ConfigurationPageType => ConfigurationPageType.PluginConfiguration;
public IPlugin Plugin => NFOGuard.Emby.Plugin.Plugin.Instance;
public Stream GetHtmlStream()
{
var type = GetType();
var stream = type.Assembly.GetManifestResourceStream("NFOGuard.Emby.Plugin.Configuration.configPage.html");
if (stream == null)
{
// Try alternative resource names
stream = type.Assembly.GetManifestResourceStream("NFOGuard.Emby.Plugin.configPage.html");
}
return stream;
}
}
}
-99
View File
@@ -1,99 +0,0 @@
using System;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Logging;
namespace NFOGuard.Emby.Plugin
{
public class PluginEntryPoint : IServerEntryPoint
{
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepository;
private readonly ILogger _logger;
private LibraryEventHandler _eventHandler;
public PluginEntryPoint(ILibraryManager libraryManager, IItemRepository itemRepository, ILogManager logManager)
{
_libraryManager = libraryManager;
_itemRepository = itemRepository;
_logger = logManager.GetLogger("NFOGuard");
}
public void Run()
{
_logger.Info("NFOGuard :: Plugin starting up");
try
{
// Initialize tier settings based on build configuration
InitializeTierSettings();
_eventHandler = new LibraryEventHandler(_libraryManager, _itemRepository, _logger);
_logger.Info("NFOGuard :: Event handlers registered successfully");
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: Failed to initialize event handlers", ex);
}
}
private void InitializeTierSettings()
{
var freeTierManager = new FreeTierManager(_logger, _libraryManager);
var config = Plugin.Instance.Configuration;
// Initialize as percentage-based free tier by default if not configured
if (config.MonthlyProcessingLimit == 0 && !config.LicenseValid)
{
freeTierManager.SetPercentageBasedLimits(); // 2% episodes, 5% movies
_logger.Info("NFOGuard :: Free tier initialized - 2% of TV episodes + 5% of movies per month. Subscribe to unlock unlimited processing.");
}
else if (config.LicenseValid)
{
_logger.Info("NFOGuard :: Licensed version active - unlimited processing enabled");
}
else
{
_logger.Info($"NFOGuard :: Free tier active - {freeTierManager.GetUsageInfo()}");
}
// Start periodic license checking
StartPeriodicLicenseCheck();
}
private async void StartPeriodicLicenseCheck()
{
try
{
// Check every 24 hours for subscription changes
var timer = new System.Threading.Timer(async _ =>
{
try
{
var licenseManager = new LicenseManager(_logger);
await licenseManager.CheckAndActivateLicenseAsync();
var config = Plugin.Instance.Configuration;
config.LastLicenseCheck = DateTime.UtcNow;
Plugin.Instance.SaveConfiguration();
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: Periodic license check failed", ex);
}
}, null, TimeSpan.FromHours(1), TimeSpan.FromHours(24)); // First check in 1 hour, then every 24 hours
}
catch (Exception ex)
{
_logger.ErrorException("NFOGuard :: Failed to start periodic license check", ex);
}
}
public void Dispose()
{
_logger.Info("NFOGuard :: Plugin shutting down");
_eventHandler?.Dispose();
}
}
}
@@ -1,35 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("NFOGuard Emby Plugin")]
[assembly: AssemblyDescription("Synchronizes TV episode PremiereDate to DateCreated to preserve import dates in Emby")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("NFOGuard")]
[assembly: AssemblyProduct("NFOGuard.Emby.Plugin")]
[assembly: AssemblyCopyright("Copyright © NFOGuard 2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("b8a7f9e2-1234-4567-8901-2b3c4d5e6f7a")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
-28
View File
@@ -1,28 +0,0 @@
# NFOGuard Emby Plugin
This is the companion Emby plugin for NFOGuard that automatically synchronizes TV episode PremiereDate to DateCreated to preserve import dates.
## Features
- Real-time processing of new and updated TV episodes
- Automatically syncs PremiereDate → DateCreated when episodes are added/scanned
- Preserves original import dates from NFO files
- Logging for debugging and monitoring
## Installation
1. Build the plugin DLL
2. Copy to Emby's plugins directory
3. Restart Emby server
4. Plugin will automatically start processing episodes
## How It Works
The plugin hooks into Emby's library events (ItemAdded/ItemUpdated) and:
1. Detects when TV episodes are processed
2. Checks if PremiereDate exists and differs from DateCreated
3. Updates DateCreated to match PremiereDate
4. Saves changes to the Emby database
This ensures that Emby displays the correct import date (from NFO dateadded) instead of the scan date.
-131
View File
@@ -1,131 +0,0 @@
# NFOGuard Emby Plugin - WSL Development Setup
This guide helps you build the NFOGuard Emby plugin on Windows using WSL (Windows Subsystem for Linux).
## Prerequisites
### 1. Install .NET SDK in WSL
```bash
# Update package list
sudo apt update
# Install .NET SDK 6.0 (supports .NET Framework 4.8 development)
sudo apt install -y dotnet-sdk-6.0
# Verify installation
dotnet --version
```
### 2. Get Emby SDK Files
You need to obtain the Emby SDK DLLs from an Emby Server installation:
**Option A: From existing Emby Server**
1. Locate your Emby Server installation directory:
- Windows: `C:\Program Files\Emby-Server\System`
- Or wherever you installed Emby Server
2. Copy these files to `NFOguard/EmbySDK/`:
- `MediaBrowser.Common.dll`
- `MediaBrowser.Controller.dll`
- `MediaBrowser.Model.dll`
**Option B: Download from official sources**
- Check the [Emby Plugin Development docs](https://dev.emby.media/doc/plugins/dev/index.html)
### 3. Prepare EmbySDK Directory
```bash
# From the NFOguard directory
mkdir -p EmbySDK
# Copy the DLL files here (use Windows Explorer or copy commands)
# You should have:
# EmbySDK/MediaBrowser.Common.dll
# EmbySDK/MediaBrowser.Controller.dll
# EmbySDK/MediaBrowser.Model.dll
```
## Building the Plugin
### Option 1: Using Build Script (Recommended)
```bash
cd NFOGuard.Emby.Plugin
./build.sh
```
### Option 2: Manual Build
```bash
cd NFOGuard.Emby.Plugin
# Clean previous builds
rm -rf bin obj
# Build the project
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48
```
## Troubleshooting
### Common Issues
**1. "MediaBrowser.* not found" errors:**
- Ensure SDK DLLs are in `../EmbySDK/` relative to the .csproj file
- Check DLL file names match exactly (case-sensitive on Linux)
**2. ".NET Framework 4.8 targeting pack" errors:**
```bash
# Install Mono for .NET Framework support
sudo apt install mono-complete
```
**3. Permission errors:**
```bash
# Make build script executable
chmod +x build.sh
```
**4. WSL file system issues:**
- Make sure you're working in the Linux filesystem (`/home/...`) not Windows mounts (`/mnt/c/...`) for better performance
- If files are on Windows side, they may have permission/line ending issues
### Verifying Build Success
After successful build:
```bash
# Check if DLL was created
ls -la bin/Release/net48/NFOGuard.Emby.Plugin.dll
# Check file size (should be > 0 bytes)
file bin/Release/net48/NFOGuard.Emby.Plugin.dll
```
## Deployment to Windows Emby Server
1. **Locate Emby plugins directory:**
- `C:\ProgramData\Emby-Server\plugins`
2. **Copy from WSL to Windows:**
```bash
# From WSL, copy to Windows (adjust path as needed)
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll /mnt/c/ProgramData/Emby-Server/plugins/
```
3. **Restart Emby Server**
4. **Verify in Emby Dashboard:**
- Go to Dashboard → Plugins
- Look for "NFOGuard" in installed plugins
## Development Tips
- Use VS Code in WSL for editing: `code .`
- Keep source files in WSL filesystem for best performance
- Only copy final DLL to Windows Emby server
- Use `tail -f /var/log/emby.log` (or wherever Emby logs are) to monitor plugin activity
## Next Steps
Once built and deployed:
1. Test with a new TV episode import
2. Check Emby logs for NFOGuard messages
3. Verify DateCreated syncs to PremiereDate in database
4. Configure licensing when ready for production use
-58
View File
@@ -1,58 +0,0 @@
#!/bin/bash
echo "Building NFOGuard Emby Plugin - Automatic Subscription System"
echo "============================================================"
echo "🔥 Like TimeLord - No manual keys, automatic activation!"
echo ""
# Clean previous builds
rm -rf bin obj
echo "Building automatic subscription version..."
# Build unified version with automatic licensing
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48
if [ $? -eq 0 ]; then
# Create version directory
mkdir -p ../releases/v2.0.0-automatic
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../releases/v2.0.0-automatic/
# Copy to main plugin-release directory
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../plugin-release/
echo "✅ Automatic subscription version built successfully"
echo ""
echo "🎉 NFOGuard v2.0.0-automatic ready!"
echo ""
echo "🔧 How It Works:"
echo "1. Plugin installs with percentage-based free tier:"
echo " • 2% of your TV episodes per month"
echo " • 5% of your movies per month"
echo " • Scheduled processing only (no real-time)"
echo ""
echo "2. Users register with email (optional but recommended)"
echo "3. Subscribe via Patreon or PayPal recurring"
echo "4. Plugin automatically detects subscription within 24hrs"
echo "5. Unlimited processing + real-time sync unlocked"
echo ""
echo "💳 Payment Options:"
echo "• Patreon: https://patreon.com/nfoguard (recommended)"
echo "• PayPal Recurring: https://nfoguard.com/paypal"
echo ""
echo "🔒 Security Features:"
echo "• Machine ID based activation (harder to crack)"
echo "• Server-side subscription validation"
echo "• Automatic deactivation if subscription expires"
echo "• Offline mode graceful fallback to free tier"
echo ""
echo "📊 API Endpoints Needed (nfoguard.com):"
echo "• POST /api/register - Machine registration"
echo "• GET /api/patreon/check?machine_id=XXX - Patreon validation"
echo "• GET /api/paypal/check?machine_id=XXX - PayPal validation"
echo ""
echo "File ready: releases/v2.0.0-automatic/NFOGuard.Emby.Plugin.dll"
else
echo "❌ Build failed"
exit 1
fi
-52
View File
@@ -1,52 +0,0 @@
#!/bin/bash
echo "Building DateSync Emby Plugin - Clean Version"
echo "============================================="
echo "Completely different plugin name to avoid Emby cache issues"
echo ""
# Clean previous builds
rm -rf bin obj
echo "Building clean version with new name..."
# Build clean version
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48
if [ $? -eq 0 ]; then
# Create version directory
mkdir -p ../releases/v1.0.4-clean
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../releases/v1.0.4-clean/DateSync.Emby.Plugin.dll
# Copy to main plugin-release directory with new name
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../plugin-release/DateSync.Emby.Plugin.dll
echo "✅ Clean version built successfully"
echo ""
echo "🧹 DateSync v1.0.4-clean ready!"
echo ""
echo "IMPORTANT CHANGES:"
echo "• Plugin name: NFOGuard → DateSync"
echo "• Config file: nfoguard.xml → datesync.xml"
echo "• Task name: NFOGuard Date Sync → DateSync Task"
echo "• DLL name: DateSync.Emby.Plugin.dll"
echo "• New GUID: Completely different from NFOGuard"
echo ""
echo "This should avoid ALL cached references!"
echo ""
echo "Usage:"
echo "1. Stop Emby Server"
echo "2. DELETE old NFOGuard.Emby.Plugin.dll completely"
echo "3. DELETE /config/data/nfoguard.xml if exists"
echo "4. Clear browser cache completely"
echo "5. Install DateSync.Emby.Plugin.dll"
echo "6. Start Emby Server"
echo "7. Look for 'DateSync Task' in scheduled tasks"
echo ""
echo "Files ready:"
echo "• releases/v1.0.4-clean/DateSync.Emby.Plugin.dll"
echo "• plugin-release/DateSync.Emby.Plugin.dll (for deployment)"
else
echo "❌ Build failed"
exit 1
fi
-46
View File
@@ -1,46 +0,0 @@
#!/bin/bash
echo "Building NFOGuard Emby Plugin - Debug Version"
echo "============================================="
echo "Clean diagnostic version with debug enabled by default"
echo ""
# Clean previous builds
rm -rf bin obj
echo "Building debug version..."
# Build debug version
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48
if [ $? -eq 0 ]; then
# Create version directory
mkdir -p ../releases/v1.0.2-debug
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../releases/v1.0.2-debug/
# Copy to main plugin-release directory for easy deployment
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../plugin-release/
echo "✅ Debug version built successfully"
echo ""
echo "🔍 NFOGuard v1.0.2-debug ready!"
echo ""
echo "Features:"
echo "• Debug logging ENABLED by default"
echo "• No configuration page (no crashes)"
echo "• Enhanced episode/movie date diagnostics"
echo "• Shows PremiereDate vs DateCreated for every item"
echo "• Explains why episodes are skipped"
echo ""
echo "Usage:"
echo "1. Replace plugin DLL and restart Emby"
echo "2. Run 'NFOGuard Date Sync' scheduled task"
echo "3. Check Emby logs for detailed output"
echo ""
echo "Files ready:"
echo "• releases/v1.0.2-debug/NFOGuard.Emby.Plugin.dll"
echo "• plugin-release/NFOGuard.Emby.Plugin.dll (for deployment)"
else
echo "❌ Build failed"
exit 1
fi
@@ -1,46 +0,0 @@
#!/bin/bash
echo "Building NFOGuard Emby Plugin - Safe Diagnostic Version"
echo "======================================================="
echo "Enhanced logging WITHOUT configuration page (to avoid crashes)"
echo ""
# Disable configuration page temporarily
mv PluginConfigurationPage.cs PluginConfigurationPage.cs.disabled 2>/dev/null || true
# Comment out the config page in project file
sed -i 's|<EmbeddedResource Include="Configuration\\configPage.html" />|<!-- <EmbeddedResource Include="Configuration\\configPage.html" /> -->|' NFOGuard.Emby.Plugin.csproj
# Clean previous builds
rm -rf bin obj
echo "Building safe diagnostic version (no config page)..."
# Build diagnostic version
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48
if [ $? -eq 0 ]; then
# Create version directory
mkdir -p ../releases/v1.0.1-diagnostic-safe
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../releases/v1.0.1-diagnostic-safe/
echo "✅ Safe diagnostic version built successfully"
echo ""
echo "🔍 NFOGuard v1.0.1-diagnostic-safe ready!"
echo ""
echo "This version includes:"
echo "• Enhanced diagnostic logging"
echo "• NO configuration page (avoids crashes)"
echo "• LogVerbose is ENABLED by default"
echo "• All other settings use defaults"
echo ""
echo "Safe to use without risk of configuration crashes!"
echo ""
echo "File ready: releases/v1.0.1-diagnostic-safe/NFOGuard.Emby.Plugin.dll"
else
echo "❌ Build failed"
fi
# Restore configuration page files
mv PluginConfigurationPage.cs.disabled PluginConfigurationPage.cs 2>/dev/null || true
sed -i 's|<!-- <EmbeddedResource Include="Configuration\\configPage.html" /> -->|<EmbeddedResource Include="Configuration\\configPage.html" />|' NFOGuard.Emby.Plugin.csproj
-43
View File
@@ -1,43 +0,0 @@
#!/bin/bash
echo "Building NFOGuard Emby Plugin - Diagnostic Version"
echo "==================================================="
echo "Enhanced logging to diagnose NFO date reading issue"
echo ""
# Clean previous builds
rm -rf bin obj
echo "Building diagnostic version..."
# Build diagnostic version
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48
if [ $? -eq 0 ]; then
# Create version directory
mkdir -p ../releases/v1.0.1-diagnostic
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../releases/v1.0.1-diagnostic/
# Copy to main plugin-release directory
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../plugin-release/
echo "✅ Diagnostic version built successfully"
echo ""
echo "🔍 NFOGuard v1.0.1-diagnostic ready!"
echo ""
echo "This version includes enhanced logging to show:"
echo "• PremiereDate values for every episode/movie"
echo "• DateCreated values for comparison"
echo "• Reason why episodes are skipped (no PremiereDate vs already synced)"
echo ""
echo "To use:"
echo "1. Replace your current plugin DLL"
echo "2. Enable 'LogVerbose' in plugin settings"
echo "3. Run the scheduled task"
echo "4. Check Emby logs for detailed date information"
echo ""
echo "File ready: releases/v1.0.1-diagnostic/NFOGuard.Emby.Plugin.dll"
else
echo "❌ Build failed"
exit 1
fi
-51
View File
@@ -1,51 +0,0 @@
#!/bin/bash
echo "Building NFOGuard Emby Plugin - Minimal Version"
echo "==============================================="
echo "No web components, no thumbnails, no config pages - just core functionality"
echo ""
# Clean previous builds
rm -rf bin obj
echo "Building minimal version..."
# Build minimal version
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48
if [ $? -eq 0 ]; then
# Create version directory
mkdir -p ../releases/v1.0.3-minimal
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../releases/v1.0.3-minimal/
# Copy to main plugin-release directory for easy deployment
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../plugin-release/
echo "✅ Minimal version built successfully"
echo ""
echo "🔍 NFOGuard v1.0.3-minimal ready!"
echo ""
echo "Features:"
echo "• Debug logging ENABLED by default"
echo "• NO web components (no UI loading issues)"
echo "• NO configuration page"
echo "• NO thumbnails or embedded resources"
echo "• Core functionality only"
echo "• New GUID to clear Emby cache completely"
echo ""
echo "This version should NOT cause web UI loading issues!"
echo ""
echo "Usage:"
echo "1. Remove old plugin DLL completely"
echo "2. Clear browser cache"
echo "3. Install new minimal DLL and restart Emby"
echo "4. Web UI should load normally"
echo "5. Run 'NFOGuard Date Sync' scheduled task"
echo ""
echo "Files ready:"
echo "• releases/v1.0.3-minimal/NFOGuard.Emby.Plugin.dll"
echo "• plugin-release/NFOGuard.Emby.Plugin.dll (for deployment)"
else
echo "❌ Build failed"
exit 1
fi
-43
View File
@@ -1,43 +0,0 @@
#!/bin/bash
echo "Building NFOGuard Emby Plugin - Unified Version"
echo "==============================================="
echo "Single DLL with license key activation system"
echo ""
# Clean previous builds
rm -rf bin obj
echo "Building unified version..."
# Build unified version without any defines
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48
if [ $? -eq 0 ]; then
# Create version directory
mkdir -p ../releases/v1.3.0-unified
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../releases/v1.3.0-unified/
# Copy to main plugin-release directory
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../plugin-release/
echo "✅ Unified version built successfully"
echo ""
echo "🎉 NFOGuard v1.3.0-unified ready!"
echo ""
echo "Features:"
echo "🆓 Free Tier: 500 items/month, scheduled processing only"
echo "🔑 License Activation: Unlimited processing with valid license key"
echo "💳 Payment Integration: Supports PayPal/Patreon transaction IDs"
echo "📊 Usage Tracking: Monthly quota with automatic reset"
echo ""
echo "License Key Formats Supported:"
echo "- NFOG-XXXX-XXXX-XXXX (generated keys)"
echo "- PAYPAL-[transaction-id] (PayPal payments)"
echo "- PATREON-[transaction-id] (Patreon subscriptions)"
echo ""
echo "File ready: releases/v1.3.0-unified/NFOGuard.Emby.Plugin.dll"
else
echo "❌ Build failed"
exit 1
fi
-60
View File
@@ -1,60 +0,0 @@
#!/bin/bash
echo "Building NFOGuard Emby Plugin - All Versions"
echo "============================================="
# Clean previous builds
rm -rf bin obj releases-temp
# Create temporary releases directory
mkdir -p releases-temp
echo ""
echo "1. Building Free Tier Version (v1.1.0-free-tier)"
echo "================================================="
# Build free tier version with FREE_TIER define
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48 -p:DefineConstants="FREE_TIER"
if [ $? -eq 0 ]; then
mkdir -p ../releases/v1.1.0-free-tier
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../releases/v1.1.0-free-tier/
echo "✅ Free tier version built successfully"
else
echo "❌ Free tier build failed"
exit 1
fi
echo ""
echo "2. Building Full Version (v1.2.0-production)"
echo "============================================"
# Clean for full version build
rm -rf bin obj
# Build full version without FREE_TIER define
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48
if [ $? -eq 0 ]; then
mkdir -p ../releases/v1.2.0-production
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../releases/v1.2.0-production/
echo "✅ Production version built successfully"
else
echo "❌ Production build failed"
exit 1
fi
echo ""
echo "3. Updating plugin-release with latest production version"
echo "========================================================"
cp bin/Release/net48/NFOGuard.Emby.Plugin.dll ../plugin-release/
echo ""
echo "🎉 All versions built successfully!"
echo ""
echo "Available versions:"
echo "📦 v1.0.0-working : Original working version (unlimited)"
echo "🆓 v1.1.0-free-tier : Free tier (500 items/month, no real-time)"
echo "💎 v1.2.0-production : Full version (unlimited, all features)"
echo ""
echo "Files ready in releases/ directory"
-41
View File
@@ -1,41 +0,0 @@
@echo off
echo Building NFOGuard Emby Plugin...
REM Check if MSBuild is available
where msbuild >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo ERROR: MSBuild not found. Please install Visual Studio Build Tools or use Developer Command Prompt.
pause
exit /b 1
)
REM Clean previous builds
if exist "bin" rmdir /s /q "bin"
if exist "obj" rmdir /s /q "obj"
REM Build the project
msbuild NFOGuard.Emby.Plugin.csproj /p:Configuration=Release /p:Platform="Any CPU"
if %ERRORLEVEL% equ 0 (
echo.
echo Build successful!
echo Plugin DLL location: bin\Release\net48\NFOGuard.Emby.Plugin.dll
echo.
REM Create plugin-release directory in repo root and copy DLL
if not exist "..\plugin-release" mkdir "..\plugin-release"
copy "bin\Release\net48\NFOGuard.Emby.Plugin.dll" "..\plugin-release\"
echo Plugin copied to: ..\plugin-release\NFOGuard.Emby.Plugin.dll
echo.
echo To install:
echo 1. Copy plugin-release\NFOGuard.Emby.Plugin.dll to your Emby plugins folder
echo 2. Restart Emby Server
echo.
) else (
echo.
echo Build failed! Check the output above for errors.
echo.
)
pause
-68
View File
@@ -1,68 +0,0 @@
#!/bin/bash
echo "Building NFOGuard Emby Plugin..."
# Check if we need to install .NET Framework support
if ! command -v dotnet &> /dev/null; then
echo "ERROR: .NET SDK not found. Installing..."
echo "Run: sudo apt update && sudo apt install -y dotnet-sdk-6.0"
exit 1
fi
# Create EmbySDK directory if it doesn't exist
if [ ! -d "../EmbySDK" ]; then
echo "Creating EmbySDK directory..."
mkdir -p ../EmbySDK
echo ""
echo "IMPORTANT: You need to copy Emby SDK DLLs to ../EmbySDK/"
echo "Required files:"
echo " - MediaBrowser.Common.dll"
echo " - MediaBrowser.Controller.dll"
echo " - MediaBrowser.Model.dll"
echo ""
echo "These can be found in your Emby Server installation directory."
echo "Continue? (y/n)"
read -r answer
if [[ $answer != "y" ]]; then
echo "Build cancelled. Please copy SDK files first."
exit 1
fi
fi
# Clean previous builds
rm -rf bin obj
# Try building with dotnet first (requires SDK), fall back to msbuild
if command -v dotnet &> /dev/null; then
echo "Building with .NET CLI..."
dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48
elif command -v msbuild &> /dev/null; then
echo "Building with MSBuild..."
msbuild NFOGuard.Emby.Plugin.csproj /p:Configuration=Release /p:Platform="Any CPU"
else
echo "ERROR: No build tool found. Install .NET SDK or Mono with MSBuild."
exit 1
fi
if [ $? -eq 0 ]; then
echo
echo "Build successful!"
echo "Plugin DLL location: bin/Release/net48/NFOGuard.Emby.Plugin.dll"
echo
# Create plugin-release directory in repo root and copy DLL
PLUGIN_RELEASE_DIR="../plugin-release"
mkdir -p "$PLUGIN_RELEASE_DIR"
cp "bin/Release/net48/NFOGuard.Emby.Plugin.dll" "$PLUGIN_RELEASE_DIR/"
echo "Plugin copied to: $PLUGIN_RELEASE_DIR/NFOGuard.Emby.Plugin.dll"
echo
echo "To install:"
echo "1. Copy plugin-release/NFOGuard.Emby.Plugin.dll to your Emby plugins folder"
echo "2. Restart Emby Server"
echo
else
echo
echo "Build failed! Check the output above for errors."
echo
fi
@@ -1,70 +0,0 @@
{
"format": 1,
"restore": {
"/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj": {}
},
"projects": {
"/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj",
"projectName": "NFOGuard.Emby.Plugin",
"projectPath": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj",
"packagesPath": "/home/jskala/.nuget/packages/",
"outputPath": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/home/jskala/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"net48"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net48": {
"targetAlias": "net48",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
}
},
"frameworks": {
"net48": {
"targetAlias": "net48",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"suppressParent": "All",
"target": "Package",
"version": "[1.0.3, )",
"autoReferenced": true
},
"System.Memory": {
"target": "Package",
"version": "[4.5.5, )"
},
"System.Net.Http": {
"target": "Package",
"version": "[4.3.4, )"
},
"System.Text.Json": {
"target": "Package",
"version": "[6.0.0, )"
}
},
"runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/8.0.414/RuntimeIdentifierGraph.json"
}
}
}
}
}
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/jskala/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/jskala/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.11.1</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/home/jskala/.nuget/packages/" />
</ItemGroup>
</Project>
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)system.text.json/6.0.0/build/System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json/6.0.0/build/System.Text.Json.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.netframework.referenceassemblies.net48/1.0.3/build/Microsoft.NETFramework.ReferenceAssemblies.net48.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.netframework.referenceassemblies.net48/1.0.3/build/Microsoft.NETFramework.ReferenceAssemblies.net48.targets')" />
</ImportGroup>
</Project>
@@ -1,4 +0,0 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
@@ -1,5 +0,0 @@
is_global = true
build_property.RootNamespace = NFOGuard.Emby.Plugin
build_property.ProjectDir = /home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
@@ -1 +0,0 @@
ac9b1d05d1517a7c47b07fdf8111e5c0cb41b1f211b4f061f560a7f28e99067e
@@ -1,17 +0,0 @@
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/NFOGuard.Emby.Plugin.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/NFOGuard.Emby.Plugin.pdb
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/Microsoft.Bcl.AsyncInterfaces.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Buffers.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Memory.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Numerics.Vectors.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Runtime.CompilerServices.Unsafe.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Text.Encodings.Web.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Text.Json.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Threading.Tasks.Extensions.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.ValueTuple.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.AssemblyReference.cache
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.GeneratedMSBuildEditorConfig.editorconfig
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.CoreCompileInputs.cache
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.F23561FE.Up2Date
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.dll
/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.pdb
File diff suppressed because it is too large Load Diff
@@ -1,38 +0,0 @@
{
"version": 2,
"dgSpecHash": "1tbmKnFkad0=",
"success": true,
"projectFilePath": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj",
"expectedPackageFiles": [
"/home/jskala/.nuget/packages/microsoft.bcl.asyncinterfaces/6.0.0/microsoft.bcl.asyncinterfaces.6.0.0.nupkg.sha512",
"/home/jskala/.nuget/packages/microsoft.netframework.referenceassemblies/1.0.3/microsoft.netframework.referenceassemblies.1.0.3.nupkg.sha512",
"/home/jskala/.nuget/packages/microsoft.netframework.referenceassemblies.net48/1.0.3/microsoft.netframework.referenceassemblies.net48.1.0.3.nupkg.sha512",
"/home/jskala/.nuget/packages/system.buffers/4.5.1/system.buffers.4.5.1.nupkg.sha512",
"/home/jskala/.nuget/packages/system.io/4.3.0/system.io.4.3.0.nupkg.sha512",
"/home/jskala/.nuget/packages/system.memory/4.5.5/system.memory.4.5.5.nupkg.sha512",
"/home/jskala/.nuget/packages/system.net.http/4.3.4/system.net.http.4.3.4.nupkg.sha512",
"/home/jskala/.nuget/packages/system.numerics.vectors/4.5.0/system.numerics.vectors.4.5.0.nupkg.sha512",
"/home/jskala/.nuget/packages/system.runtime/4.3.0/system.runtime.4.3.0.nupkg.sha512",
"/home/jskala/.nuget/packages/system.runtime.compilerservices.unsafe/6.0.0/system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512",
"/home/jskala/.nuget/packages/system.security.cryptography.algorithms/4.3.0/system.security.cryptography.algorithms.4.3.0.nupkg.sha512",
"/home/jskala/.nuget/packages/system.security.cryptography.encoding/4.3.0/system.security.cryptography.encoding.4.3.0.nupkg.sha512",
"/home/jskala/.nuget/packages/system.security.cryptography.primitives/4.3.0/system.security.cryptography.primitives.4.3.0.nupkg.sha512",
"/home/jskala/.nuget/packages/system.security.cryptography.x509certificates/4.3.0/system.security.cryptography.x509certificates.4.3.0.nupkg.sha512",
"/home/jskala/.nuget/packages/system.text.encodings.web/6.0.0/system.text.encodings.web.6.0.0.nupkg.sha512",
"/home/jskala/.nuget/packages/system.text.json/6.0.0/system.text.json.6.0.0.nupkg.sha512",
"/home/jskala/.nuget/packages/system.threading.tasks.extensions/4.5.4/system.threading.tasks.extensions.4.5.4.nupkg.sha512",
"/home/jskala/.nuget/packages/system.valuetuple/4.5.0/system.valuetuple.4.5.0.nupkg.sha512"
],
"logs": [
{
"code": "NU1903",
"level": "Warning",
"warningLevel": 1,
"message": "Package 'System.Text.Json' 6.0.0 has a known high severity vulnerability, https://github.com/advisories/GHSA-8g4q-xg66-9fp4",
"libraryId": "System.Text.Json",
"targetGraphs": [
".NETFramework,Version=v4.8"
]
}
]
}
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PluginInfo>
<Name>NFOGuard</Name>
<Description>Synchronizes TV episode PremiereDate to DateCreated to preserve import dates in Emby</Description>
<Overview>This plugin automatically syncs PremiereDate to DateCreated for TV episodes to ensure Emby displays the correct import date from NFO files instead of scan dates.</Overview>
<Version>1.0.0.0</Version>
<MinServerVersion>4.7.0.0</MinServerVersion>
<MaxServerVersion>4.8.99.99</MaxServerVersion>
<Author>NFOGuard Team</Author>
<Category>General</Category>
<ImageUrl>thumb.png</ImageUrl>
<PluginUrl>https://github.com/NFOguard/NFOguard</PluginUrl>
<IsPremium>false</IsPremium>
<IsHidden>false</IsHidden>
<TargetFilename>NFOGuard.Emby.Plugin.dll</TargetFilename>
<SourceUrl>https://github.com/NFOguard/NFOguard/releases/latest/NFOGuard.Emby.Plugin.dll</SourceUrl>
<Changelog>
<Version number="1.0.0.0">
<Description>Initial release - TV episode date synchronization</Description>
</Version>
</Changelog>
</PluginInfo>
+5 -4
View File
@@ -1,12 +1,13 @@
# NFOGuard
*** Alpha in progress things are volatile use at your own risk ***
**NFOGuard** is a lightweight webhook service that locks in the *true import date* of your movies and TV episodes, ensuring that upgrades, renames, or reimports never bubble old media up as “new” in Emby, Jellyfin, or Plex.
**NFOGuard** is a lightweight webhook service that locks in the *true import date* of your movies and TV episodes, ensuring that upgrades, renames, or reimports never bubble old media up as "new" in Emby, Jellyfin, or Plex.
It integrates with **Sonarr** and **Radarr**, listens for import/rename/upgrade events, and automatically manages `.nfo` files and filesystem timestamps to keep your library chronology consistent.
## Companion Plugin
For **Emby users**, check out the companion [NFOGuard Emby Plugin](https://github.com/jskala/NFOGuard-Emby-Plugin) that provides real-time date synchronization within Emby itself.
---
## ✨ Features
+62
View File
@@ -0,0 +1,62 @@
# NFOGuard Webhook Repository Structure
This document shows the clean structure for the NFOGuard webhook Docker application repository.
## Core Application Files
```
nfoguard.py # Main webhook server application
requirements.txt # Python dependencies
Dockerfile # Container build configuration
VERSION # Version tracking
```
## Source Code Modules
```
core/
├── database.py # SQLite database management
├── nfo_manager.py # NFO file creation and management
├── path_mapper.py # Path mapping between containers
└── logging.py # Logging configuration
clients/
├── external_clients.py # TMDB, OMDb, TVDB, Jellyseerr API clients
├── radarr_client.py # Radarr API client
├── radarr_db_client.py # Radarr database client
└── sonarr_client.py # Sonarr API client
```
## Configuration & Deployment
```
docker-compose.yml # Public example docker-compose (uses published image)
.env.template # Environment configuration template
.env.secrets.template # API keys template (includes TVDB_API_KEY)
```
## Documentation
```
README.md # Main documentation (mentions companion plugin)
DEPLOYMENT.md # Deployment guide
SETUP.md # Setup instructions
TESTING.md # Testing guide
CHANGELOG.md # Version history
LICENSE # MIT license
SUMMARY.md # Project summary
projectsummary.md # Project overview
```
## Development & Utilities
```
test_movie_scan.py # Movie scanning tests
test_end_to_end.py # End-to-end integration tests
test_bulk_update.py # Bulk update testing
bulk_update_movies.py # Movie bulk update utility
```
## Files NOT in Repository
- Plugin source code (moved to NFOGuard-Emby-Plugin repo)
- CI/registry configuration files
- Build artifacts
- Development docker-compose files
- Registry troubleshooting files
## Total: Clean, focused webhook Docker application repository
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# Script to clean up CI/development files
echo "Removing CI/development artifacts..."
rm -f gitea-registry-troubleshooting.md
rm -f gitea-packages-config.ini
rm -f find-gitea-local-ip.sh
rm -f example-multi-registry-workflow.yml
rm -f example-docker-compose.yml
rm -f example-distribution-README.md
rm -f docker-compose.example.yml
rm -f cleanup-github.sh
rm -f check-gitea-registry.sh
echo "Cleanup complete!"
echo "Removed CI/registry-specific files from webhook repository"
@@ -1,246 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Security;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
namespace Emby.Security;
public class PluginSecurityManager : IServerEntryPoint, IDisposable
{
private const string MBValidateUrl = "https://mb3admin.com/admin/service/registration/validate";
private readonly IHttpClient _httpClient;
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly ICryptoProvider _cryptographyProvider;
private readonly ISecurityManager _securityManager;
private readonly IEncryptionManager _encryption;
public static PluginSecurityManager Current;
private readonly Dictionary<string, RegRecord> _regRecordCache = new Dictionary<string, RegRecord>();
private SemaphoreSlim _regLock = new SemaphoreSlim(1, 1);
public PluginSecurityManager(IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem, ICryptoProvider cryptographyProvider, ISecurityManager securityManager, IEncryptionManager encryption)
{
if (httpClient == null)
{
throw new ArgumentNullException("httpClient");
}
_appHost = appHost;
_httpClient = httpClient;
_jsonSerializer = jsonSerializer;
_appPaths = appPaths;
_fileSystem = fileSystem;
_cryptographyProvider = cryptographyProvider;
_securityManager = securityManager;
_encryption = encryption;
_logger = logManager.GetLogger("Plugin");
Current = this;
}
public void Run()
{
}
public void Dispose()
{
}
private string GetCacheFilePath(string feature)
{
string text = GetType().FullName + GetCacheKey(feature);
text = BaseExtensions.GetMD5(text).ToString("N");
return Path.Combine(_appPaths.PluginConfigurationsPath, text);
}
private RegRecord GetCachedRegRecord(string feature)
{
lock (_regRecordCache)
{
if (_regRecordCache.TryGetValue(GetCacheKey(feature), out var value))
{
return value;
}
}
string cacheFilePath = GetCacheFilePath(feature);
try
{
string text = _encryption.DecryptString(_fileSystem.ReadAllText(cacheFilePath));
RegRecord value = _jsonSerializer.DeserializeFromString<RegRecord>(text);
if (value != null)
{
return value;
}
}
catch (Exception)
{
}
return null;
}
private string GetCacheKey(string feature)
{
string text = _securityManager.SupporterKey;
if (string.IsNullOrWhiteSpace(text))
{
text = "none";
}
return text + "-" + feature;
}
private void DeleteCachedRecord(string feature)
{
lock (_regRecordCache)
{
_regRecordCache.Remove(GetCacheKey(feature));
}
try
{
_fileSystem.DeleteFile(GetCacheFilePath(feature));
}
catch
{
}
}
private void SaveCachedRecord(string feature, RegRecord record)
{
lock (_regRecordCache)
{
_regRecordCache[GetCacheKey(feature)] = record;
}
string cacheFilePath = GetCacheFilePath(feature);
string text = _jsonSerializer.SerializeToString((object)record);
string text2 = _encryption.EncryptString(text);
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath));
_fileSystem.WriteAllText(cacheFilePath, text2);
}
public async Task<RegRecord> GetRegistrationStatus(string feature, CancellationToken cancellationToken)
{
await _regLock.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
try
{
return await GetRegistrationStatusInternal(feature, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
}
finally
{
_regLock.Release();
}
}
private bool CheckRegistrationWithServer(DateTime lastChecked, bool registered)
{
if (registered || string.IsNullOrWhiteSpace(_securityManager.SupporterKey))
{
if (DateTime.UtcNow.AddDays(-1.0) > lastChecked)
{
return true;
}
}
else if (DateTime.UtcNow.AddHours(-1.0) > lastChecked)
{
return true;
}
return false;
}
private async Task<RegRecord> GetRegistrationStatusInternal(string feature, CancellationToken cancellationToken)
{
RegRecord regInfo = GetCachedRegRecord(feature);
DateTime lastChecked = regInfo?.lastChecked ?? DateTime.MinValue;
DateTime expDate = regInfo?.expDate ?? DateTime.MinValue;
bool registered = regInfo?.registered ?? false;
string supporterKey = _securityManager.SupporterKey;
if (CheckRegistrationWithServer(lastChecked, registered))
{
Dictionary<string, string> postData = new Dictionary<string, string>
{
{ "feature", feature },
{ "key", supporterKey },
{
"mac",
((IApplicationHost)_appHost).SystemId
},
{
"systemid",
((IApplicationHost)_appHost).SystemId
},
{
"platform",
((IApplicationHost)_appHost).OperatingSystemDisplayName
}
};
try
{
HttpRequestOptions val = new HttpRequestOptions
{
Url = "https://mb3admin.com/admin/service/registration/validate",
EnableHttpCompression = false,
BufferContent = false,
CancellationToken = cancellationToken
};
val.SetPostData((IDictionary<string, string>)postData);
HttpResponseInfo val2 = await _httpClient.SendAsync(val, "POST").ConfigureAwait(continueOnCapturedContext: false);
try
{
using Stream stream = val2.Content;
regInfo = _jsonSerializer.DeserializeFromStream<RegRecord>(stream);
regInfo.lastChecked = DateTime.UtcNow;
SaveCachedRecord(feature, regInfo);
}
finally
{
((IDisposable)val2)?.Dispose();
}
}
catch (Exception ex)
{
_logger.ErrorException("Error checking registration status of {0}", ex, new object[1] { feature });
int num = 14;
DateTime dateTime = new DateTime[2]
{
expDate,
lastChecked.AddDays(num)
}.Min();
if (dateTime > DateTime.UtcNow.AddDays(num))
{
dateTime = DateTime.MinValue;
}
regInfo = new RegRecord
{
registered = (regInfo != null && regInfo.registered && dateTime >= DateTime.UtcNow && expDate >= DateTime.UtcNow),
expDate = expDate
};
}
}
return regInfo;
}
}
@@ -1,40 +0,0 @@
using System;
namespace Emby.Security;
public class RegRecord
{
public string featId { get; set; }
public bool registered { get; set; }
public DateTime expDate { get; set; }
public string key { get; set; }
public DateTime lastChecked { get; set; }
public bool isTrial
{
get
{
if (expDate > DateTime.UtcNow)
{
return !registered;
}
return false;
}
}
public bool isValid
{
get
{
if (!isTrial)
{
return registered;
}
return true;
}
}
}
@@ -1,258 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Security;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Security;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Plugins.TimeLord.ScheduledTasks;
public class TLTVScheduledTask : IScheduledTask
{
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly IApplicationPaths _appPaths;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IUserManager _userManager;
private readonly INotificationManager _notificationManager;
private readonly IItemRepository _itemRepo;
public string Category => "Time Lord";
public string Key => "TimeLordTV";
public string Description => "Sets the Episode Date Added to the original airdate (Episodes)";
public string Name => "Change episode date added to original airdate";
public TLTVScheduledTask(ILibraryManager libraryManager, ILogger logger, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IApplicationPaths appPaths, ILibraryMonitor libraryMonitor, ISecurityManager securityManager, IUserManager userManager, INotificationManager notificationManager, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_userManager = userManager;
_mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_appPaths = appPaths;
_libraryMonitor = libraryMonitor;
_notificationManager = notificationManager;
_itemRepo = itemRepo;
}
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
RegRecord registration = await PluginSecurityManager.Current.GetRegistrationStatus("TimeLord", cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
Plugin.Instance.Registration = registration;
_logger.Info("TimeLordTV Registration Status - Registered: {0} In trial: {2} Expiration Date: {1} Is Valid: {3}", new object[4]
{
Plugin.Instance.Registration.registered,
Plugin.Instance.Registration.expDate,
Plugin.Instance.Registration.isTrial,
Plugin.Instance.Registration.isValid
});
if (Plugin.Instance.Registration.isTrial)
{
_logger.Info(((BasePlugin)Plugin.Instance).Name + " You are Using the Trial Version, Please Consider purchasing to continue using the plugin after the trial period", new object[0]);
}
if (!Plugin.Instance.Registration.isValid)
{
_logger.Info(((BasePlugin)Plugin.Instance).Name + " Trial Expired, Please register to continue using the plugin", new object[0]);
return;
}
_logger.Info("TimeLordTV :: Start :", (object[])null);
try
{
_logger.Info("TimeLord :: Rework Series Dates :: Start", (object[])null);
AggregateFolder rootFolder = _libraryManager.RootFolder;
InternalItemsQuery val = new InternalItemsQuery();
val.IncludeItemTypes = new string[1] { typeof(Series).Name };
val.Recursive = true;
BaseItem[] itemList = ((Folder)rootFolder).GetItemList(val);
int num = 0;
BaseItem[] array = itemList;
foreach (BaseItem val2 in array)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
if (val2.PremiereDate.HasValue && val2.DateCreated != val2.PremiereDate.Value && val2.PremiereDate.HasValue)
{
val2.DateCreated = val2.PremiereDate.Value;
try
{
val2.UpdateToRepository((ItemUpdateType)16);
}
catch
{
}
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex2)
{
_logger.ErrorException("TimeLordTV :: Error messing about with {0}", ex2, new object[1] { val2.Name });
}
num++;
double num2 = num;
num2 *= 100.0;
progress.Report(num2);
}
}
catch (Exception ex3)
{
_logger.ErrorException("TimeLord :: Error {0}", ex3, Array.Empty<object>());
}
try
{
_logger.Info("TimeLord :: Rework Season Dates :: Start", (object[])null);
AggregateFolder rootFolder2 = _libraryManager.RootFolder;
InternalItemsQuery val = new InternalItemsQuery();
val.IncludeItemTypes = new string[1] { typeof(Season).Name };
val.Recursive = true;
BaseItem[] itemList2 = ((Folder)rootFolder2).GetItemList(val);
_logger.Info("TimeLord :: Rework Season Dates :: Count ::" + itemList2.Count(), (object[])null);
int num3 = 0;
BaseItem[] array = itemList2;
for (int i = 0; i < array.Length; i++)
{
Season val3 = (Season)array[i];
try
{
cancellationToken.ThrowIfCancellationRequested();
if (((BaseItem)val3).PremiereDate.HasValue && ((BaseItem)val3).DateCreated != ((BaseItem)val3).PremiereDate.Value && ((BaseItem)val3).PremiereDate.HasValue)
{
((BaseItem)val3).DateCreated = ((BaseItem)val3).PremiereDate.Value;
try
{
((BaseItem)val3).UpdateToRepository((ItemUpdateType)16);
_logger.Info("TimeLordTV :: Updated :: " + ((BaseItem)val3).Name, (object[])null);
}
catch
{
}
}
}
catch (OperationCanceledException)
{
_logger.Info("TimeLordTV :: Canceled", (object[])null);
break;
}
catch (Exception ex5)
{
_logger.ErrorException("TimeLordTV :: Error messing about with {0}", ex5, new object[1] { ((BaseItem)val3).Name });
}
num3++;
double num4 = num3;
num4 /= (double)itemList2.Count();
num4 *= 100.0;
progress.Report(num4);
}
}
catch (Exception ex6)
{
_logger.ErrorException("TimeLord :: Error {0}", ex6, Array.Empty<object>());
}
try
{
_logger.Info("TimeLord :: Rework Episode Dates :: Start", (object[])null);
AggregateFolder rootFolder3 = _libraryManager.RootFolder;
InternalItemsQuery val = new InternalItemsQuery();
val.IncludeItemTypes = new string[1] { typeof(Episode).Name };
val.Recursive = true;
List<BaseItem> list = ((Folder)rootFolder3).GetItemList(val).Where(delegate(BaseItem val5)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
if ((int)val5.LocationType == 0)
{
DateTimeOffset dateCreated = val5.DateCreated;
DateTimeOffset? premiereDate = val5.PremiereDate;
if (premiereDate.HasValue && dateCreated > premiereDate.GetValueOrDefault() && val5.PremiereDate.HasValue)
{
return !val5.IsLocked;
}
}
return false;
}).ToList();
_logger.Info("TimeLord :: Rework Episode Dates :: Count ::" + list.Count, (object[])null);
int num5 = 0;
foreach (Episode item in list)
{
Episode val4 = item;
try
{
cancellationToken.ThrowIfCancellationRequested();
if (((BaseItem)val4).PremiereDate.HasValue && ((BaseItem)val4).DateCreated != ((BaseItem)val4).PremiereDate.Value && ((BaseItem)val4).PremiereDate.HasValue)
{
((BaseItem)val4).DateCreated = ((BaseItem)val4).PremiereDate.Value;
try
{
((BaseItem)val4).UpdateToRepository((ItemUpdateType)16);
_logger.Info("TimeLordTV :: Updated :: " + ((BaseItem)val4).Name, (object[])null);
}
catch
{
}
}
}
catch (OperationCanceledException)
{
_logger.Info("TimeLordTV :: Canceled", (object[])null);
break;
}
catch (Exception ex8)
{
_logger.ErrorException("TimeLordTV :: Error messing about with {0}", ex8, new object[1] { ((BaseItem)val4).Name });
}
num5++;
double num6 = num5;
num6 /= (double)list.Count;
num6 *= 100.0;
progress.Report(num6);
}
}
catch (Exception ex9)
{
_logger.ErrorException("TimeLord :: Error {0}", ex9, Array.Empty<object>());
}
}
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Expected O, but got Unknown
return (IEnumerable<TaskTriggerInfo>)(object)new TaskTriggerInfo[1]
{
new TaskTriggerInfo
{
Type = "DailyTrigger",
TimeOfDayTicks = TimeSpan.FromHours(5.0).Ticks
}
};
}
}
@@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>MediaBrowser.Plugins.TimeLord</AssemblyName>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<LangVersion>12.0</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="MediaBrowser.Plugins.TimeLord.thumb.png" />
<EmbeddedResource Include="MediaBrowser.Plugins.TimeLord.thumb.png" LogicalName="MediaBrowser.Plugins.TimeLord.thumb.png" />
</ItemGroup>
<ItemGroup>
<Reference Include="MediaBrowser.Controller" />
<Reference Include="MediaBrowser.Common" />
<Reference Include="MediaBrowser.Model" />
</ItemGroup>
</Project>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

@@ -1,35 +0,0 @@
using System;
using System.IO;
using Emby.Security;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Drawing;
namespace MediaBrowser.Plugins.TimeLord;
public class Plugin : BasePlugin, IHasThumbImage
{
private Guid _id = new Guid("3A7EF733-F70B-4947-9E03-2813B5A423E3");
public RegRecord Registration;
public override string Name => "TimeLordTV";
public override string Description => "Set Episode Added date to the original airdate for the Epsiode.";
public override Guid Id => _id;
public ImageFormat ThumbImageFormat => (ImageFormat)3;
public static Plugin Instance { get; private set; }
public Stream GetThumbImage()
{
Type type = ((object)this).GetType();
return type.Assembly.GetManifestResourceStream(type.Namespace + ".thumb.png");
}
public Plugin()
{
Instance = this;
}
}
@@ -1,17 +0,0 @@
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
[assembly: ComVisible(false)]
[assembly: Guid("3A7EF733-F70B-4947-9E03-2813B5A423E3")]
[assembly: AssemblyCompany("ScoobydooProject.com")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © 2019 ScoobydooProject.com")]
[assembly: AssemblyDescription("Wayback Machine for TV Episodes")]
[assembly: AssemblyFileVersion("19.08.03.1105")]
[assembly: AssemblyInformationalVersion("3.2.32.14")]
[assembly: AssemblyProduct("MediaBrowser.Plugins.TimeLord")]
[assembly: AssemblyTitle("MediaBrowser.Plugins.TimeLord")]
[assembly: AssemblyVersion("19.8.3.1105")]
@@ -1,246 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Security;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
namespace Emby.Security;
public class PluginSecurityManager : IServerEntryPoint, IDisposable
{
private const string MBValidateUrl = "https://mb3admin.com/admin/service/registration/validate";
private readonly IHttpClient _httpClient;
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly ICryptoProvider _cryptographyProvider;
private readonly ISecurityManager _securityManager;
private readonly IEncryptionManager _encryption;
public static PluginSecurityManager Current;
private readonly Dictionary<string, RegRecord> _regRecordCache = new Dictionary<string, RegRecord>();
private SemaphoreSlim _regLock = new SemaphoreSlim(1, 1);
public PluginSecurityManager(IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem, ICryptoProvider cryptographyProvider, ISecurityManager securityManager, IEncryptionManager encryption)
{
if (httpClient == null)
{
throw new ArgumentNullException("httpClient");
}
_appHost = appHost;
_httpClient = httpClient;
_jsonSerializer = jsonSerializer;
_appPaths = appPaths;
_fileSystem = fileSystem;
_cryptographyProvider = cryptographyProvider;
_securityManager = securityManager;
_encryption = encryption;
_logger = logManager.GetLogger("Plugin");
Current = this;
}
public void Run()
{
}
public void Dispose()
{
}
private string GetCacheFilePath(string feature)
{
string text = GetType().FullName + GetCacheKey(feature);
text = BaseExtensions.GetMD5(text).ToString("N");
return Path.Combine(_appPaths.PluginConfigurationsPath, text);
}
private RegRecord GetCachedRegRecord(string feature)
{
lock (_regRecordCache)
{
if (_regRecordCache.TryGetValue(GetCacheKey(feature), out var value))
{
return value;
}
}
string cacheFilePath = GetCacheFilePath(feature);
try
{
string text = _encryption.DecryptString(_fileSystem.ReadAllText(cacheFilePath));
RegRecord value = _jsonSerializer.DeserializeFromString<RegRecord>(text);
if (value != null)
{
return value;
}
}
catch (Exception)
{
}
return null;
}
private string GetCacheKey(string feature)
{
string text = _securityManager.SupporterKey;
if (string.IsNullOrWhiteSpace(text))
{
text = "none";
}
return text + "-" + feature;
}
private void DeleteCachedRecord(string feature)
{
lock (_regRecordCache)
{
_regRecordCache.Remove(GetCacheKey(feature));
}
try
{
_fileSystem.DeleteFile(GetCacheFilePath(feature));
}
catch
{
}
}
private void SaveCachedRecord(string feature, RegRecord record)
{
lock (_regRecordCache)
{
_regRecordCache[GetCacheKey(feature)] = record;
}
string cacheFilePath = GetCacheFilePath(feature);
string text = _jsonSerializer.SerializeToString((object)record);
string text2 = _encryption.EncryptString(text);
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath));
_fileSystem.WriteAllText(cacheFilePath, text2);
}
public async Task<RegRecord> GetRegistrationStatus(string feature, CancellationToken cancellationToken)
{
await _regLock.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
try
{
return await GetRegistrationStatusInternal(feature, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
}
finally
{
_regLock.Release();
}
}
private bool CheckRegistrationWithServer(DateTime lastChecked, bool registered)
{
if (registered || string.IsNullOrWhiteSpace(_securityManager.SupporterKey))
{
if (DateTime.UtcNow.AddDays(-1.0) > lastChecked)
{
return true;
}
}
else if (DateTime.UtcNow.AddHours(-1.0) > lastChecked)
{
return true;
}
return false;
}
private async Task<RegRecord> GetRegistrationStatusInternal(string feature, CancellationToken cancellationToken)
{
RegRecord regInfo = GetCachedRegRecord(feature);
DateTime lastChecked = regInfo?.lastChecked ?? DateTime.MinValue;
DateTime expDate = regInfo?.expDate ?? DateTime.MinValue;
bool registered = regInfo?.registered ?? false;
string supporterKey = _securityManager.SupporterKey;
if (CheckRegistrationWithServer(lastChecked, registered))
{
Dictionary<string, string> postData = new Dictionary<string, string>
{
{ "feature", feature },
{ "key", supporterKey },
{
"mac",
((IApplicationHost)_appHost).SystemId
},
{
"systemid",
((IApplicationHost)_appHost).SystemId
},
{
"platform",
((IApplicationHost)_appHost).OperatingSystemDisplayName
}
};
try
{
HttpRequestOptions val = new HttpRequestOptions
{
Url = "https://mb3admin.com/admin/service/registration/validate",
EnableHttpCompression = false,
BufferContent = false,
CancellationToken = cancellationToken
};
val.SetPostData((IDictionary<string, string>)postData);
HttpResponseInfo val2 = await _httpClient.SendAsync(val, "POST").ConfigureAwait(continueOnCapturedContext: false);
try
{
using Stream stream = val2.Content;
regInfo = _jsonSerializer.DeserializeFromStream<RegRecord>(stream);
regInfo.lastChecked = DateTime.UtcNow;
SaveCachedRecord(feature, regInfo);
}
finally
{
((IDisposable)val2)?.Dispose();
}
}
catch (Exception ex)
{
_logger.ErrorException("Error checking registration status of {0}", ex, new object[1] { feature });
int num = 14;
DateTime dateTime = new DateTime[2]
{
expDate,
lastChecked.AddDays(num)
}.Min();
if (dateTime > DateTime.UtcNow.AddDays(num))
{
dateTime = DateTime.MinValue;
}
regInfo = new RegRecord
{
registered = (regInfo != null && regInfo.registered && dateTime >= DateTime.UtcNow && expDate >= DateTime.UtcNow),
expDate = expDate
};
}
}
return regInfo;
}
}
@@ -1,40 +0,0 @@
using System;
namespace Emby.Security;
public class RegRecord
{
public string featId { get; set; }
public bool registered { get; set; }
public DateTime expDate { get; set; }
public string key { get; set; }
public DateTime lastChecked { get; set; }
public bool isTrial
{
get
{
if (expDate > DateTime.UtcNow)
{
return !registered;
}
return false;
}
}
public bool isValid
{
get
{
if (!isTrial)
{
return registered;
}
return true;
}
}
}
@@ -1,160 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Security;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Security;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Plugins.TimeLordMovies.ScheduledTasks;
public class TLMoviesScheduledTask : IScheduledTask
{
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly IApplicationPaths _appPaths;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IUserManager _userManager;
private readonly INotificationManager _notificationManager;
private readonly IItemRepository _itemRepo;
public string Category => "Time Lord";
public string Key => "TimeLordMovies";
public string Description => "Sets the Movie Date Added to the original premier date (Movies)";
public string Name => "Change Movie date added to original Premier date";
public TLMoviesScheduledTask(ILibraryManager libraryManager, ILogger logger, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IApplicationPaths appPaths, ILibraryMonitor libraryMonitor, ISecurityManager securityManager, IUserManager userManager, INotificationManager notificationManager, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_userManager = userManager;
_mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_appPaths = appPaths;
_libraryMonitor = libraryMonitor;
_notificationManager = notificationManager;
_itemRepo = itemRepo;
}
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
RegRecord registration = await PluginSecurityManager.Current.GetRegistrationStatus("TimeLordMovies", cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
Plugin.Instance.Registration = registration;
_logger.Info("TimeLordMovies Registration Status - Registered: {0} In trial: {2} Expiration Date: {1} Is Valid: {3}", new object[4]
{
Plugin.Instance.Registration.registered,
Plugin.Instance.Registration.expDate,
Plugin.Instance.Registration.isTrial,
Plugin.Instance.Registration.isValid
});
if (Plugin.Instance.Registration.isTrial)
{
_logger.Info(((BasePlugin)Plugin.Instance).Name + " You are Using the Trial Version, Please Consider purchasing to continue using the plugin after the trial period", new object[0]);
}
if (!Plugin.Instance.Registration.isValid)
{
_logger.Info(((BasePlugin)Plugin.Instance).Name + " Trial Expired, Please register to continue using the plugin", new object[0]);
return;
}
_logger.Info("TimeLord.Movies :: Start :", (object[])null);
try
{
_logger.Info("TimeLord.Movies :: Rework Movie Dates :: Start", (object[])null);
AggregateFolder rootFolder = _libraryManager.RootFolder;
InternalItemsQuery val = new InternalItemsQuery();
val.IncludeItemTypes = new string[1] { typeof(Movie).Name };
val.Recursive = true;
List<BaseItem> list = ((Folder)rootFolder).GetItemList(val).Where(delegate(BaseItem i)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
if ((int)i.LocationType == 0)
{
DateTimeOffset dateCreated = i.DateCreated;
DateTimeOffset? premiereDate = i.PremiereDate;
if (premiereDate.HasValue && dateCreated > premiereDate.GetValueOrDefault() && i.PremiereDate.HasValue)
{
return !i.IsLocked;
}
}
return false;
}).ToList();
int num = 0;
foreach (Movie item in list)
{
Movie val2 = item;
try
{
cancellationToken.ThrowIfCancellationRequested();
((BaseItem)val2).DateCreated = ((BaseItem)val2).PremiereDate.Value;
try
{
((BaseItem)val2).UpdateToRepository((ItemUpdateType)16);
_logger.Info("TimeLord.Movies:: Updated :: " + ((BaseItem)val2).Name, (object[])null);
}
catch
{
_logger.Info("TimeLord.Movies :: Updated :: " + ((BaseItem)val2).Name, (object[])null);
}
}
catch (OperationCanceledException)
{
_logger.Info("TimeLord.Movies :: Canceled", (object[])null);
break;
}
catch (Exception ex2)
{
_logger.ErrorException("TimeLord.Movies :: Error messing about with {0}", ex2, new object[1] { ((BaseItem)val2).Name });
}
num++;
double num2 = num;
num2 /= (double)list.Count();
num2 *= 100.0;
progress.Report(num2);
}
}
catch (Exception ex3)
{
_logger.ErrorException("TimeLord.Movies :: Error {0}", ex3, Array.Empty<object>());
}
}
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Expected O, but got Unknown
return (IEnumerable<TaskTriggerInfo>)(object)new TaskTriggerInfo[1]
{
new TaskTriggerInfo
{
Type = "DailyTrigger",
TimeOfDayTicks = TimeSpan.FromHours(5.0).Ticks
}
};
}
}
@@ -1,159 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Security;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Security;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Plugins.TimeLordMovies.ScheduledTasks;
public class TLTrailersScheduledTask : IScheduledTask
{
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly IApplicationPaths _appPaths;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IUserManager _userManager;
private readonly INotificationManager _notificationManager;
private readonly IItemRepository _itemRepo;
public string Category => "Time Lord";
public string Key => "TimeLordMovies";
public string Description => "Sets the Trailer Date Added to the original premier date (Trailer)";
public string Name => "Change Trailer date added to original Premier date";
public TLTrailersScheduledTask(ILibraryManager libraryManager, ILogger logger, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IApplicationPaths appPaths, ILibraryMonitor libraryMonitor, ISecurityManager securityManager, IUserManager userManager, INotificationManager notificationManager, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_userManager = userManager;
_mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_appPaths = appPaths;
_libraryMonitor = libraryMonitor;
_notificationManager = notificationManager;
_itemRepo = itemRepo;
}
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
RegRecord registration = await PluginSecurityManager.Current.GetRegistrationStatus("TimeLordMovies", cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
Plugin.Instance.Registration = registration;
_logger.Info("TimeLordMovies Registration Status - Registered: {0} In trial: {2} Expiration Date: {1} Is Valid: {3}", new object[4]
{
Plugin.Instance.Registration.registered,
Plugin.Instance.Registration.expDate,
Plugin.Instance.Registration.isTrial,
Plugin.Instance.Registration.isValid
});
if (Plugin.Instance.Registration.isTrial)
{
_logger.Info(((BasePlugin)Plugin.Instance).Name + " You are Using the Trial Version, Please Consider purchasing to continue using the plugin after the trial period", new object[0]);
}
if (!Plugin.Instance.Registration.isValid)
{
_logger.Info(((BasePlugin)Plugin.Instance).Name + " Trial Expired, Please register to continue using the plugin", new object[0]);
return;
}
_logger.Info("TimeLordMovies :: Start :", (object[])null);
try
{
_logger.Info("TimeLord :: Rework Trailer Dates :: Start", (object[])null);
AggregateFolder rootFolder = _libraryManager.RootFolder;
InternalItemsQuery val = new InternalItemsQuery();
val.IncludeItemTypes = new string[1] { typeof(Trailer).Name };
val.Recursive = true;
List<BaseItem> list = ((Folder)rootFolder).GetItemList(val).Where(delegate(BaseItem i)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
if ((int)i.LocationType != 0)
{
DateTimeOffset dateCreated = i.DateCreated;
DateTimeOffset? premiereDate = i.PremiereDate;
if (premiereDate.HasValue && dateCreated > premiereDate.GetValueOrDefault() && i.PremiereDate.HasValue)
{
return !i.IsLocked;
}
}
return false;
}).ToList();
int num = 0;
foreach (Trailer item in list)
{
Trailer val2 = item;
try
{
cancellationToken.ThrowIfCancellationRequested();
((BaseItem)val2).DateCreated = ((BaseItem)val2).PremiereDate.Value;
try
{
((BaseItem)val2).UpdateToRepository((ItemUpdateType)16);
_logger.Info("TimeLord.Trailers :: Updated :: " + ((BaseItem)val2).Name, (object[])null);
}
catch
{
_logger.Info("TimeLord.Trailers :: Updated :: " + ((BaseItem)val2).Name, (object[])null);
}
}
catch (OperationCanceledException)
{
_logger.Info("TimeLord.Trailers :: Canceled", (object[])null);
break;
}
catch (Exception ex2)
{
_logger.ErrorException("TimeLord.Trailers :: Error messing about with {0}", ex2, new object[1] { ((BaseItem)val2).Name });
}
num++;
double num2 = num;
num2 /= (double)list.Count();
num2 *= 100.0;
progress.Report(num2);
}
}
catch (Exception ex3)
{
_logger.ErrorException("TimeLord.Trailers :: Error {0}", ex3, Array.Empty<object>());
}
}
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Expected O, but got Unknown
return (IEnumerable<TaskTriggerInfo>)(object)new TaskTriggerInfo[1]
{
new TaskTriggerInfo
{
Type = "DailyTrigger",
TimeOfDayTicks = TimeSpan.FromHours(5.0).Ticks
}
};
}
}
@@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>MediaBrowser.Plugins.TimeLordMovies</AssemblyName>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<LangVersion>12.0</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="MediaBrowser.Plugins.TimeLordMovies.thumb.png" />
<EmbeddedResource Include="MediaBrowser.Plugins.TimeLordMovies.thumb.png" LogicalName="MediaBrowser.Plugins.TimeLordMovies.thumb.png" />
</ItemGroup>
<ItemGroup>
<Reference Include="MediaBrowser.Controller" />
<Reference Include="MediaBrowser.Common" />
<Reference Include="MediaBrowser.Model" />
</ItemGroup>
</Project>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

@@ -1,35 +0,0 @@
using System;
using System.IO;
using Emby.Security;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Drawing;
namespace MediaBrowser.Plugins.TimeLordMovies;
public class Plugin : BasePlugin, IHasThumbImage
{
private Guid _id = new Guid("71a3ff1b-c589-4a39-b948-12f08b7715ee");
public RegRecord Registration;
public override string Name => "TimeLordMovies";
public override string Description => "TimeLord Movies allows you to set the date added for Movies to the original Theatrical release date set in the metadata for items in your collection. This is usefull when adding old Movies to your collection, as it prevents theses 'New' Movies from dominating your recently added list. Take control of time with TimeLord Movies.";
public override Guid Id => _id;
public ImageFormat ThumbImageFormat => (ImageFormat)3;
public static Plugin Instance { get; private set; }
public Stream GetThumbImage()
{
Type type = ((object)this).GetType();
return type.Assembly.GetManifestResourceStream(type.Namespace + ".thumb.png");
}
public Plugin()
{
Instance = this;
}
}
@@ -1,17 +0,0 @@
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
[assembly: ComVisible(false)]
[assembly: Guid("71a3ff1b-c589-4a39-b948-12f08b7715ee")]
[assembly: AssemblyCompany("ScoobydooProject.com")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © 2019 ScoobydooProject.com")]
[assembly: AssemblyDescription("Wayback Machine for TV Movies")]
[assembly: AssemblyFileVersion("19.08.03.1105")]
[assembly: AssemblyInformationalVersion("3.2.32.14")]
[assembly: AssemblyProduct("MediaBrowser.Plugins.TimeLordMovies")]
[assembly: AssemblyTitle("MediaBrowser.Plugins.TimeLordMovies")]
[assembly: AssemblyVersion("19.8.3.1105")]
+23 -24
View File
@@ -2,31 +2,19 @@ version: '3.8'
services:
nfoguard:
# image: ghcr.io/sbcrumb/nfoguard:latest # Use this once package is public
build: . # Temporary local build
image: ghcr.io/jskala/nfoguard:latest
container_name: nfoguard
restart: unless-stopped
user: "1000:1000"
networks:
- saltbox
ports:
- "8085:8080"
- "8080:8080"
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
volumes:
# Media directory
- /mnt/unionfs/Media/:/media:rw
# NFOGuard data directory for database and logs
- ./data:/app/data:rw
# Configuration files (SECURE TWO-FILE SYSTEM)
- ./.env:/app/.env:ro # Main config (safe to share)
- ./.env.secrets:/app/.env.secrets:ro # API keys & passwords (never commit)
# Optional: Can use env_file for simple setups, but file mounting is more secure
# env_file:
# - .env
# - .env.secrets
- ./data:/app/data
- ./config:/app/config
- ${MEDIA_PATH}:/media:ro
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
@@ -34,6 +22,17 @@ services:
retries: 3
start_period: 40s
networks:
saltbox:
external: true
# Optional: PostgreSQL database
# postgres:
# image: postgres:15
# container_name: nfoguard-db
# restart: unless-stopped
# environment:
# POSTGRES_DB: nfoguard
# POSTGRES_USER: ${DB_USER:-nfoguard}
# POSTGRES_PASSWORD: ${DB_PASSWORD}
# volumes:
# - postgres_data:/var/lib/postgresql/data
# volumes:
# postgres_data:
-77
View File
@@ -1,77 +0,0 @@
version: '3.8'
services:
nfoguard:
build: .
container_name: nfoguard
ports:
- "${PORT:-8080}:8080"
volumes:
# Mount your media directories (adjust these paths to match your setup)
- "${HOST_MEDIA_PATH:-/mnt/unionfs/Media}:/media:rw"
# NFOGuard data directory for database and logs
- "./data:/app/data"
# Optional: Mount config if you want to override
# - "./config:/app/config"
environment:
# Media paths inside container - customize these in your .env file
- TV_PATHS=${TV_PATHS:-/media/tv,/media/tv6}
- MOVIE_PATHS=${MOVIE_PATHS:-/media/movies,/media/movies6}
# Database
- DB_PATH=${DB_PATH:-/app/data/media_dates.db}
# Radarr Database (REQUIRED for database-only mode)
- RADARR_DB_HOST=${RADARR_DB_HOST}
- RADARR_DB_PORT=${RADARR_DB_PORT:-5432}
- RADARR_DB_NAME=${RADARR_DB_NAME:-radarr}
- RADARR_DB_USER=${RADARR_DB_USER}
- RADARR_DB_PASSWORD=${RADARR_DB_PASSWORD}
# Optional Radarr API (fallback only)
- RADARR_URL=${RADARR_URL:-}
- RADARR_API_KEY=${RADARR_API_KEY:-}
# Optional Sonarr API (for TV processing)
- SONARR_URL=${SONARR_URL:-}
- SONARR_API_KEY=${SONARR_API_KEY:-}
# NFO Management
- MANAGE_NFO=${MANAGE_NFO:-true}
- FIX_DIR_MTIMES=${FIX_DIR_MTIMES:-true}
- LOCK_METADATA=${LOCK_METADATA:-true}
- MANAGER_BRAND=${MANAGER_BRAND:-NFOGuard}
# Processing settings
- BATCH_DELAY=${BATCH_DELAY:-5.0}
- MAX_CONCURRENT_SERIES=${MAX_CONCURRENT_SERIES:-3}
- MOVIE_PRIORITY=${MOVIE_PRIORITY:-import_then_digital}
- MOVIE_POLL_MODE=${MOVIE_POLL_MODE:-always}
- MOVIE_DATE_UPDATE_MODE=${MOVIE_DATE_UPDATE_MODE:-backfill_only}
# Logging
- DEBUG=${DEBUG:-false}
# Server
- PORT=${PORT:-8080}
# Optional: Connect to Radarr's database network
# networks:
# - radarr_network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Optional: If you need to connect to Radarr's database network
# networks:
# radarr_network:
# external: true
-38
View File
@@ -1,38 +0,0 @@
version: '3.8'
services:
nfoguard:
image: ghcr.io/jskala/nfoguard:latest
container_name: nfoguard
restart: unless-stopped
ports:
- "8080:8080"
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
volumes:
- ./data:/app/data
- ./config:/app/config
- ${MEDIA_PATH}:/media:ro
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Optional: PostgreSQL database
# postgres:
# image: postgres:15
# container_name: nfoguard-db
# restart: unless-stopped
# environment:
# POSTGRES_DB: nfoguard
# POSTGRES_USER: ${DB_USER:-nfoguard}
# POSTGRES_PASSWORD: ${DB_PASSWORD}
# volumes:
# - postgres_data:/var/lib/postgresql/data
# volumes:
# postgres_data:
-70
View File
@@ -1,70 +0,0 @@
name: Build & Deploy
on:
push:
branches: [ main ]
tags: [ 'v*' ]
jobs:
build-and-push:
runs-on: host
steps:
- name: Checkout code
run: |
rm -rf * .git* 2>/dev/null || true
git clone https://gitea.skalas.org/jskala/NFOguard.git /tmp/repo
cp -r /tmp/repo/* .
cp -r /tmp/repo/.* . 2>/dev/null || true
rm -rf /tmp/repo
- name: Set up variables
run: |
# Get version from tag or use 'latest'
if [[ ${{ github.ref }} == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
else
VERSION=latest
fi
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Building version: $VERSION"
- name: Build Docker image
run: |
docker build -t nfoguard:${{ env.VERSION }} .
docker tag nfoguard:${{ env.VERSION }} nfoguard:latest
- name: Push to GitHub Container Registry
run: |
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker tag nfoguard:${{ env.VERSION }} ghcr.io/jskala/nfoguard:${{ env.VERSION }}
docker tag nfoguard:${{ env.VERSION }} ghcr.io/jskala/nfoguard:latest
docker push ghcr.io/jskala/nfoguard:${{ env.VERSION }}
docker push ghcr.io/jskala/nfoguard:latest
- name: Push to Docker Hub
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker tag nfoguard:${{ env.VERSION }} jskala/nfoguard:${{ env.VERSION }}
docker tag nfoguard:${{ env.VERSION }} jskala/nfoguard:latest
docker push jskala/nfoguard:${{ env.VERSION }}
docker push jskala/nfoguard:latest
- name: Update distribution repo
run: |
# Clone the distribution repo
git clone https://github.com/jskala/NFOguard-Deploy.git /tmp/deploy
cd /tmp/deploy
# Update docker-compose.yml with new version
sed -i "s|image: .*nfoguard:.*|image: ghcr.io/jskala/nfoguard:${{ env.VERSION }}|" docker-compose.yml
# Update README with new version info
sed -i "s/Latest Version: .*/Latest Version: ${{ env.VERSION }}/" README.md
# Commit and push changes
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add .
git commit -m "Update to version ${{ env.VERSION }}" || exit 0
git push
Binary file not shown.
Binary file not shown.
-28
View File
@@ -1,28 +0,0 @@
# NFOGuard Emby Plugin Releases
This directory contains versioned releases of the NFOGuard Emby plugin DLL.
## Release Structure
```
releases/
├── v1.0.0-working/ # Current proven working version
├── v1.1.0-free-tier/ # Version with free tier limitations
├── v1.2.0-production/ # Full production version
└── changelog.md # Version history and changes
```
## Installation
1. Choose your version from the appropriate folder
2. Copy `NFOGuard.Emby.Plugin.dll` to your Emby plugins folder
3. Restart Emby Server
4. Check version in Emby Dashboard → Plugins
## Version Guidelines
- **Working versions**: Proven stable in testing
- **Free tier versions**: Limited functionality for trial users
- **Production versions**: Full-featured, licensed versions
## Current Status
- **Latest Working**: v1.0.0 - Full functionality, no restrictions
- **Latest Free Tier**: Not yet released
- **Latest Production**: Not yet released
Binary file not shown.
-37
View File
@@ -1,37 +0,0 @@
# NFOGuard Emby Plugin v1.0.0 - Working Version
**Release Date**: 2025-01-12
**Status**: ✅ PROVEN WORKING
**License**: Full functionality, no restrictions
## 🎉 Test Results
- **Episodes Processed**: 15,039
- **Successfully Updated**: 14,661
- **Success Rate**: 97.5%
- **Processing Speed**: ~1,500 episodes/second
## ✅ Features
- **Real-time Processing**: Automatic sync on media add/update
- **Scheduled Task**: Weekly batch processing (enabled by default)
- **TV Episodes Support**: Full episode date synchronization
- **Movies Support**: Full movie date synchronization
- **High Performance**: Processes thousands of items in seconds
## 🔧 Configuration
- `EnableRealTimeSync = true` (default)
- `EnableScheduledTask = true` (default)
- `LogVerbose = false` (default)
## 📋 Installation
1. Copy `NFOGuard.Emby.Plugin.dll` to Emby plugins folder
2. Restart Emby Server
3. Plugin loads automatically - no configuration needed
4. Check "Scheduled Tasks" for "NFOGuard Date Sync"
## 🎯 What It Does
Syncs `PremiereDate``DateCreated` so Emby displays import dates from NFO files instead of scan dates.
## ⚠️ Notes
- No configuration UI (disabled to prevent crashes)
- All settings hardcoded to optimal values
- Licensing framework present but disabled
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.