mirror of
https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer.git
synced 2026-03-01 11:21:12 +03:00
320 lines
14 KiB
C#
320 lines
14 KiB
C#
using Jellyfin2SamsungCrossOS.Models;
|
|
using Jellyfin2SamsungCrossOS.Services;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Jellyfin2SamsungCrossOS.Helpers
|
|
{
|
|
public class JellyfinHelper
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
private readonly ProcessHelper _processHelper;
|
|
public JellyfinHelper(
|
|
HttpClient httpClient,
|
|
ProcessHelper processHelper)
|
|
{
|
|
_httpClient = httpClient;
|
|
_processHelper = processHelper;
|
|
}
|
|
|
|
public static bool IsValidJellyfinConfiguration()
|
|
{
|
|
return !string.IsNullOrEmpty(AppSettings.Default.JellyfinIP) &&
|
|
!string.IsNullOrEmpty(AppSettings.Default.JellyfinApiKey) &&
|
|
AppSettings.Default.JellyfinApiKey.Length == 32 &&
|
|
IsValidUrl($"http://{AppSettings.Default.JellyfinIP}/Users");
|
|
}
|
|
|
|
public static bool IsValidUrl(string url)
|
|
{
|
|
return Uri.TryCreate(url, UriKind.Absolute, out var uriResult)
|
|
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
|
|
}
|
|
|
|
public async Task<List<JellyfinAuth>> LoadJellyfinUsersAsync()
|
|
{
|
|
var users = new List<JellyfinAuth>();
|
|
|
|
if (!IsValidJellyfinConfiguration())
|
|
{
|
|
Debug.WriteLine("Invalid Jellyfin configuration - skipping user load");
|
|
return users;
|
|
}
|
|
|
|
try
|
|
{
|
|
_httpClient.DefaultRequestHeaders.Clear();
|
|
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("SamsungJellyfinInstaller/1.0");
|
|
_httpClient.DefaultRequestHeaders.Add("Authorization", $"MediaBrowser Token=\"{AppSettings.Default.JellyfinApiKey}\"");
|
|
|
|
var url = $"http://{AppSettings.Default.JellyfinIP}/Users";
|
|
Debug.WriteLine($"Attempting to load users from: {url}");
|
|
|
|
using var response = await _httpClient.GetAsync(url);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var json = await response.Content.ReadAsStringAsync();
|
|
var jellyfinUsers = JsonSerializer.Deserialize<List<JellyfinAuth>>(json, new JsonSerializerOptions
|
|
{
|
|
PropertyNameCaseInsensitive = true
|
|
});
|
|
|
|
if (jellyfinUsers != null && jellyfinUsers.Any())
|
|
{
|
|
users.AddRange(jellyfinUsers);
|
|
|
|
if (users.Count > 1)
|
|
{
|
|
users.Add(new JellyfinAuth
|
|
{
|
|
Id = "everyone",
|
|
Name = "Everyone"
|
|
});
|
|
}
|
|
|
|
Debug.WriteLine($"Successfully loaded {users.Count} users");
|
|
}
|
|
else
|
|
{
|
|
Debug.WriteLine("No users found in response");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.WriteLine($"Failed to load users - Status: {response.StatusCode}, Reason: {response.ReasonPhrase}");
|
|
}
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
Debug.WriteLine($"HTTP error loading Jellyfin users: {ex.Message}");
|
|
}
|
|
catch (TaskCanceledException ex)
|
|
{
|
|
Debug.WriteLine($"Request timeout loading Jellyfin users: {ex.Message}");
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
Debug.WriteLine($"JSON parsing error: {ex.Message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Unexpected error loading Jellyfin users: {ex.Message}");
|
|
}
|
|
|
|
return users;
|
|
}
|
|
public async Task<InstallResult> ApplyConfigAndResignPackageAsync(
|
|
string TizenCliPath,
|
|
string packagePath,
|
|
string certificateName,
|
|
string[] userIds)
|
|
{
|
|
string? tempDir = null;
|
|
string? tempPackage = null;
|
|
|
|
try
|
|
{
|
|
var baseDir = Path.GetDirectoryName(packagePath)
|
|
?? throw new InvalidOperationException("Invalid package path");
|
|
|
|
tempDir = Path.Combine(baseDir, $"JellyTemp_{Guid.NewGuid():N}");
|
|
Directory.CreateDirectory(tempDir);
|
|
|
|
ZipFile.ExtractToDirectory(packagePath, tempDir);
|
|
|
|
if (AppSettings.Default.ConfigUpdateMode.Contains("Server") ||
|
|
AppSettings.Default.ConfigUpdateMode.Contains("All"))
|
|
{
|
|
Debug.WriteLine(tempDir);
|
|
UpdateMultiServerConfig(tempDir);
|
|
}
|
|
|
|
if (AppSettings.Default.ConfigUpdateMode.Contains("Browser") ||
|
|
AppSettings.Default.ConfigUpdateMode.Contains("All"))
|
|
{
|
|
await InjectUserSettingsScriptAsync(tempDir, userIds);
|
|
}
|
|
|
|
tempPackage = Path.Combine(baseDir, $"{Path.GetFileNameWithoutExtension(packagePath)}_mod.wgt");
|
|
if (File.Exists(tempPackage)) File.Delete(tempPackage);
|
|
ZipFile.CreateFromDirectory(tempDir, tempPackage);
|
|
|
|
File.Delete(packagePath);
|
|
File.Move(tempPackage, packagePath);
|
|
|
|
await _processHelper.RunCommandAsync(TizenCliPath, $"sign --signing-profile {certificateName} \"{packagePath}\"");
|
|
|
|
return InstallResult.SuccessResult();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return InstallResult.FailureResult($"Error modifying Jellyfin package: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
if (Directory.Exists(tempDir))
|
|
Directory.Delete(tempDir, true);
|
|
|
|
if (tempPackage != null && File.Exists(tempPackage))
|
|
File.Delete(tempPackage);
|
|
}
|
|
}
|
|
|
|
public static async Task UpdateMultiServerConfig(string tempDirectory)
|
|
{
|
|
string configPath = Path.Combine(tempDirectory, "www", "config.json");
|
|
|
|
if (!File.Exists(configPath))
|
|
throw new FileNotFoundException("Jellyfin config.json not found", configPath);
|
|
|
|
string jsonText = await File.ReadAllTextAsync(configPath);
|
|
|
|
if (string.IsNullOrWhiteSpace(jsonText))
|
|
throw new InvalidDataException("config.json is empty or invalid");
|
|
|
|
var config = JsonNode.Parse(jsonText) ?? throw new JsonException("Failed to parse config.json");
|
|
config["multiserver"] = false;
|
|
|
|
string serverUrl = $"http://{AppSettings.Default.JellyfinIP}";
|
|
config["servers"] = new JsonArray { JsonValue.Create(serverUrl) };
|
|
|
|
var options = new JsonSerializerOptions { WriteIndented = true };
|
|
await File.WriteAllTextAsync(configPath, config.ToJsonString(options));
|
|
}
|
|
public static async Task InjectUserSettingsScriptAsync(string tempDirectory, string[] userIds)
|
|
{
|
|
if (userIds == null || userIds.Length == 0)
|
|
return;
|
|
|
|
string indexPath = Path.Combine(tempDirectory, "index.html");
|
|
|
|
if (!File.Exists(indexPath))
|
|
throw new FileNotFoundException("index.html not found", indexPath);
|
|
|
|
string htmlContent = await File.ReadAllTextAsync(indexPath);
|
|
|
|
const string injectionMarker = "<!-- SAMSUNG_JELLYFIN_AUTO_INJECTED -->";
|
|
if (htmlContent.Contains(injectionMarker))
|
|
return;
|
|
|
|
string BoolToJsString(bool value) => value.ToString().ToLower();
|
|
|
|
var scriptBuilder = new StringBuilder();
|
|
scriptBuilder.AppendLine(injectionMarker);
|
|
scriptBuilder.AppendLine("<script>");
|
|
scriptBuilder.AppendLine("(function() {");
|
|
|
|
foreach (string userId in userIds.Where(u => !string.IsNullOrWhiteSpace(u)))
|
|
{
|
|
scriptBuilder.AppendLine($" // Settings for user: {userId}");
|
|
scriptBuilder.AppendLine($" var userId = '{userId}';");
|
|
scriptBuilder.AppendLine();
|
|
scriptBuilder.AppendLine(" if (localStorage.getItem('samsung-jellyfin-injected-' + userId)) return;");
|
|
scriptBuilder.AppendLine();
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-appTheme', '{AppSettings.Default.SelectedTheme ?? "dark"}');");
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-enableBackdrops', '{BoolToJsString(AppSettings.Default.EnableBackdrops)}');");
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-enableThemeSongs', '{BoolToJsString(AppSettings.Default.EnableThemeSongs)}');");
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-enableThemeVideos', '{BoolToJsString(AppSettings.Default.EnableThemeVideos)}');");
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-backdropScreensaver', '{BoolToJsString(AppSettings.Default.BackdropScreensaver)}');");
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-detailsBanner', '{BoolToJsString(AppSettings.Default.DetailsBanner)}');");
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-cinemaMode', '{BoolToJsString(AppSettings.Default.CinemaMode)}');");
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-nextUpEnabled', '{BoolToJsString(AppSettings.Default.NextUpEnabled)}');");
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-enableExternalVideoPlayers', '{BoolToJsString(AppSettings.Default.EnableExternalVideoPlayers)}');");
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-skipIntros', '{BoolToJsString(AppSettings.Default.SkipIntros)}');");
|
|
scriptBuilder.AppendLine();
|
|
|
|
// Language preferences
|
|
if (!string.IsNullOrWhiteSpace(AppSettings.Default.AudioLanguagePreference))
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-audioLanguagePreference', '{AppSettings.Default.AudioLanguagePreference}');");
|
|
|
|
if (!string.IsNullOrWhiteSpace(AppSettings.Default.SubtitleLanguagePreference))
|
|
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-subtitleLanguagePreference', '{AppSettings.Default.SubtitleLanguagePreference}');");
|
|
|
|
// Mark injected
|
|
scriptBuilder.AppendLine(" localStorage.setItem('samsung-jellyfin-injected-' + userId, 'true');");
|
|
scriptBuilder.AppendLine();
|
|
}
|
|
|
|
scriptBuilder.AppendLine("})();");
|
|
scriptBuilder.AppendLine("</script>");
|
|
|
|
htmlContent = htmlContent.Replace("<head>", "<head>" + scriptBuilder.ToString());
|
|
await File.WriteAllTextAsync(indexPath, htmlContent);
|
|
}
|
|
public async Task UpdateJellyfinUsersAsync(string[] userIds)
|
|
{
|
|
if (userIds == null || userIds.Length == 0)
|
|
return;
|
|
|
|
try
|
|
{
|
|
_httpClient.DefaultRequestHeaders.Clear();
|
|
_httpClient.DefaultRequestHeaders.Add("Authorization", $"MediaBrowser Token=\"{AppSettings.Default.JellyfinApiKey}\"");
|
|
|
|
foreach (string userId in userIds.Where(u => !string.IsNullOrWhiteSpace(u)))
|
|
{
|
|
try
|
|
{
|
|
// Fetch user info
|
|
var getUserResponse = await _httpClient.GetAsync($"http://{AppSettings.Default.JellyfinIP}/Users/{userId}");
|
|
getUserResponse.EnsureSuccessStatusCode();
|
|
var userJson = await getUserResponse.Content.ReadAsStringAsync();
|
|
|
|
var userNode = JsonNode.Parse(userJson) ?? throw new JsonException("Failed to parse user JSON");
|
|
|
|
// Update auto-login setting
|
|
userNode["EnableAutoLogin"] = AppSettings.Default.UserAutoLogin;
|
|
|
|
var userContent = new StringContent(
|
|
userNode.ToJsonString(new JsonSerializerOptions { WriteIndented = true }),
|
|
Encoding.UTF8,
|
|
"application/json");
|
|
|
|
using (userContent)
|
|
{
|
|
var userResponse = await _httpClient.PostAsync($"http://{AppSettings.Default.JellyfinIP}/Users?userId={userId}", userContent);
|
|
userResponse.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
// Update additional user configurations
|
|
var userConfig = new
|
|
{
|
|
PlayDefaultAudioTrack = AppSettings.Default.PlayDefaultAudioTrack,
|
|
SubtitleLanguagePreference = AppSettings.Default.SubtitleLanguagePreference,
|
|
SubtitleMode = AppSettings.Default.SelectedSubtitleMode,
|
|
RememberAudioSelections = AppSettings.Default.RememberAudioSelections,
|
|
RememberSubtitleSelections = AppSettings.Default.RememberSubtitleSelections,
|
|
EnableNextEpisodeAutoPlay = AppSettings.Default.AutoPlayNextEpisode,
|
|
};
|
|
|
|
var configJson = JsonSerializer.Serialize(userConfig, new JsonSerializerOptions { WriteIndented = true });
|
|
|
|
using var configContent = new StringContent(configJson, Encoding.UTF8, "application/json");
|
|
var configResponse = await _httpClient.PostAsync($"http://{AppSettings.Default.JellyfinIP}/Users/Configuration?userId={userId}", configContent);
|
|
configResponse.EnsureSuccessStatusCode();
|
|
}
|
|
catch (Exception userEx)
|
|
{
|
|
Debug.WriteLine($"Failed to update configuration for user {userId}: {userEx.Message}");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"General error updating user configurations: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|