mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Fix Jellyfin playlists tab: linked Spotify ID, track count, and user/library filters
This commit is contained in:
@@ -595,10 +595,10 @@ public class AdminController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all playlists from Jellyfin
|
||||
/// Get all Jellyfin users
|
||||
/// </summary>
|
||||
[HttpGet("jellyfin/playlists")]
|
||||
public async Task<IActionResult> GetJellyfinPlaylists()
|
||||
[HttpGet("jellyfin/users")]
|
||||
public async Task<IActionResult> GetJellyfinUsers()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_jellyfinSettings.Url) || string.IsNullOrEmpty(_jellyfinSettings.ApiKey))
|
||||
{
|
||||
@@ -607,8 +607,117 @@ public class AdminController : ControllerBase
|
||||
|
||||
try
|
||||
{
|
||||
// Call Jellyfin API to get all playlists
|
||||
var url = $"{_jellyfinSettings.Url}/Items?IncludeItemTypes=Playlist&Recursive=true&Fields=ProviderIds,ChildCount";
|
||||
var url = $"{_jellyfinSettings.Url}/Users";
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.Add("X-Emby-Authorization", GetJellyfinAuthHeader());
|
||||
|
||||
var response = await _jellyfinHttpClient.SendAsync(request);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogError("Failed to fetch Jellyfin users: {StatusCode} - {Body}", response.StatusCode, errorBody);
|
||||
return StatusCode((int)response.StatusCode, new { error = "Failed to fetch users from Jellyfin" });
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
|
||||
var users = new List<object>();
|
||||
|
||||
foreach (var user in doc.RootElement.EnumerateArray())
|
||||
{
|
||||
var id = user.GetProperty("Id").GetString();
|
||||
var name = user.GetProperty("Name").GetString();
|
||||
|
||||
users.Add(new { id, name });
|
||||
}
|
||||
|
||||
return Ok(new { users });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching Jellyfin users");
|
||||
return StatusCode(500, new { error = "Failed to fetch users", details = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all Jellyfin libraries (virtual folders)
|
||||
/// </summary>
|
||||
[HttpGet("jellyfin/libraries")]
|
||||
public async Task<IActionResult> GetJellyfinLibraries()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_jellyfinSettings.Url) || string.IsNullOrEmpty(_jellyfinSettings.ApiKey))
|
||||
{
|
||||
return BadRequest(new { error = "Jellyfin URL or API key not configured" });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var url = $"{_jellyfinSettings.Url}/Library/VirtualFolders";
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.Add("X-Emby-Authorization", GetJellyfinAuthHeader());
|
||||
|
||||
var response = await _jellyfinHttpClient.SendAsync(request);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogError("Failed to fetch Jellyfin libraries: {StatusCode} - {Body}", response.StatusCode, errorBody);
|
||||
return StatusCode((int)response.StatusCode, new { error = "Failed to fetch libraries from Jellyfin" });
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
|
||||
var libraries = new List<object>();
|
||||
|
||||
foreach (var lib in doc.RootElement.EnumerateArray())
|
||||
{
|
||||
var name = lib.GetProperty("Name").GetString();
|
||||
var itemId = lib.TryGetProperty("ItemId", out var id) ? id.GetString() : null;
|
||||
var collectionType = lib.TryGetProperty("CollectionType", out var ct) ? ct.GetString() : null;
|
||||
|
||||
libraries.Add(new { id = itemId, name, collectionType });
|
||||
}
|
||||
|
||||
return Ok(new { libraries });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching Jellyfin libraries");
|
||||
return StatusCode(500, new { error = "Failed to fetch libraries", details = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all playlists from Jellyfin
|
||||
/// </summary>
|
||||
[HttpGet("jellyfin/playlists")]
|
||||
public async Task<IActionResult> GetJellyfinPlaylists([FromQuery] string? userId = null, [FromQuery] string? parentId = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_jellyfinSettings.Url) || string.IsNullOrEmpty(_jellyfinSettings.ApiKey))
|
||||
{
|
||||
return BadRequest(new { error = "Jellyfin URL or API key not configured" });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Build URL with optional userId and parentId (library) filters
|
||||
var url = $"{_jellyfinSettings.Url}/Items?IncludeItemTypes=Playlist&Recursive=true&Fields=ProviderIds,ChildCount,RecursiveItemCount,SongCount";
|
||||
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
url += $"&UserId={userId}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(parentId))
|
||||
{
|
||||
url += $"&ParentId={parentId}";
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.Add("X-Emby-Authorization", GetJellyfinAuthHeader());
|
||||
@@ -633,21 +742,21 @@ public class AdminController : ControllerBase
|
||||
{
|
||||
var id = item.GetProperty("Id").GetString();
|
||||
var name = item.GetProperty("Name").GetString();
|
||||
var childCount = item.TryGetProperty("ChildCount", out var cc) ? cc.GetInt32() : 0;
|
||||
|
||||
// Check if this playlist has a linked Spotify ID in ProviderIds
|
||||
string? linkedSpotifyId = null;
|
||||
if (item.TryGetProperty("ProviderIds", out var providerIds))
|
||||
{
|
||||
if (providerIds.TryGetProperty("Spotify", out var spotifyId))
|
||||
{
|
||||
linkedSpotifyId = spotifyId.GetString();
|
||||
}
|
||||
}
|
||||
// Try multiple fields for track count - Jellyfin may use different fields
|
||||
var childCount = 0;
|
||||
if (item.TryGetProperty("ChildCount", out var cc) && cc.ValueKind == JsonValueKind.Number)
|
||||
childCount = cc.GetInt32();
|
||||
else if (item.TryGetProperty("SongCount", out var sc) && sc.ValueKind == JsonValueKind.Number)
|
||||
childCount = sc.GetInt32();
|
||||
else if (item.TryGetProperty("RecursiveItemCount", out var ric) && ric.ValueKind == JsonValueKind.Number)
|
||||
childCount = ric.GetInt32();
|
||||
|
||||
// Check if this playlist is already configured in allstarr
|
||||
var isConfigured = _spotifyImportSettings.Playlists.Any(p =>
|
||||
p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
// Check if this playlist is configured in allstarr and get linked Spotify ID
|
||||
var configuredPlaylist = _spotifyImportSettings.Playlists
|
||||
.FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
var isConfigured = configuredPlaylist != null;
|
||||
var linkedSpotifyId = configuredPlaylist?.Id;
|
||||
|
||||
playlists.Add(new
|
||||
{
|
||||
|
||||
@@ -565,8 +565,24 @@
|
||||
</h2>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 16px;">
|
||||
Link Jellyfin playlists to Spotify playlists to fill in missing tracks.
|
||||
Playlists created by Spotify Import plugin will appear here.
|
||||
Select a user and/or library to filter playlists.
|
||||
</p>
|
||||
|
||||
<div style="display: flex; gap: 16px; margin-bottom: 16px; flex-wrap: wrap;">
|
||||
<div class="form-group" style="margin: 0; flex: 1; min-width: 200px;">
|
||||
<label style="display: block; margin-bottom: 4px; color: var(--text-secondary); font-size: 0.85rem;">User</label>
|
||||
<select id="jellyfin-user-select" onchange="fetchJellyfinPlaylists()" style="width: 100%; padding: 8px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 6px; color: var(--text-primary);">
|
||||
<option value="">All Users</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="margin: 0; flex: 1; min-width: 200px;">
|
||||
<label style="display: block; margin-bottom: 4px; color: var(--text-secondary); font-size: 0.85rem;">Library</label>
|
||||
<select id="jellyfin-library-select" onchange="fetchJellyfinPlaylists()" style="width: 100%; padding: 8px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 6px; color: var(--text-primary);">
|
||||
<option value="">All Libraries</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="playlist-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -1047,12 +1063,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJellyfinUsers() {
|
||||
try {
|
||||
const res = await fetch('/api/admin/jellyfin/users');
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const select = document.getElementById('jellyfin-user-select');
|
||||
select.innerHTML = '<option value="">All Users</option>' +
|
||||
data.users.map(u => `<option value="${u.id}">${escapeHtml(u.name)}</option>`).join('');
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch users:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJellyfinLibraries() {
|
||||
try {
|
||||
const res = await fetch('/api/admin/jellyfin/libraries');
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const select = document.getElementById('jellyfin-library-select');
|
||||
select.innerHTML = '<option value="">All Libraries</option>' +
|
||||
data.libraries.map(l => `<option value="${l.id}">${escapeHtml(l.name)}${l.collectionType ? ' (' + l.collectionType + ')' : ''}</option>`).join('');
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch libraries:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJellyfinPlaylists() {
|
||||
const tbody = document.getElementById('jellyfin-playlist-table-body');
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="loading"><span class="spinner"></span> Loading Jellyfin playlists...</td></tr>';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/admin/jellyfin/playlists');
|
||||
// Build URL with optional filters
|
||||
const userId = document.getElementById('jellyfin-user-select').value;
|
||||
const parentId = document.getElementById('jellyfin-library-select').value;
|
||||
|
||||
let url = '/api/admin/jellyfin/playlists';
|
||||
const params = new URLSearchParams();
|
||||
if (userId) params.append('userId', userId);
|
||||
if (parentId) params.append('parentId', parentId);
|
||||
if (params.toString()) url += '?' + params.toString();
|
||||
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json();
|
||||
@@ -1415,6 +1467,8 @@
|
||||
// Initial load
|
||||
fetchStatus();
|
||||
fetchPlaylists();
|
||||
fetchJellyfinUsers();
|
||||
fetchJellyfinLibraries();
|
||||
fetchJellyfinPlaylists();
|
||||
fetchConfig();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user