mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-10 16:08:39 -05:00
feat: Fork octo-fiestarr as allstarr with Jellyfin proxy improvements
Major changes: - Rename project from octo-fiesta to allstarr - Add Jellyfin proxy support alongside Subsonic/Navidrome - Implement fuzzy search with relevance scoring and Levenshtein distance - Add POST body logging for debugging playback progress issues - Separate local and external artists in search results - Add +5 score boost for external results to prioritize larger catalog(probably gonna reverse it) - Create FuzzyMatcher utility for intelligent search result scoring - Add ConvertPlaylistToJellyfinItem method for playlist support - Rename keys folder to apis and update gitignore - Filter search results by relevance score (>= 40) - Add Redis caching support with configurable settings - Update environment configuration with backend selection - Improve external provider integration (SquidWTF, Deezer, Qobuz) - Add tests for all services
This commit is contained in:
20
allstarr/Models/Domain/Album.cs
Normal file
20
allstarr/Models/Domain/Album.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace allstarr.Models.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an album
|
||||
/// </summary>
|
||||
public class Album
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Artist { get; set; } = string.Empty;
|
||||
public string? ArtistId { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public int? SongCount { get; set; }
|
||||
public string? CoverArtUrl { get; set; }
|
||||
public string? Genre { get; set; }
|
||||
public bool IsLocal { get; set; }
|
||||
public string? ExternalProvider { get; set; }
|
||||
public string? ExternalId { get; set; }
|
||||
public List<Song> Songs { get; set; } = new();
|
||||
}
|
||||
15
allstarr/Models/Domain/Artist.cs
Normal file
15
allstarr/Models/Domain/Artist.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace allstarr.Models.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an artist
|
||||
/// </summary>
|
||||
public class Artist
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? ImageUrl { get; set; }
|
||||
public int? AlbumCount { get; set; }
|
||||
public bool IsLocal { get; set; }
|
||||
public string? ExternalProvider { get; set; }
|
||||
public string? ExternalId { get; set; }
|
||||
}
|
||||
97
allstarr/Models/Domain/Song.cs
Normal file
97
allstarr/Models/Domain/Song.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
namespace allstarr.Models.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a song (local or external)
|
||||
/// </summary>
|
||||
public class Song
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique ID. For external songs, prefixed with "ext-" + provider + "-" + external id
|
||||
/// Example: "ext-deezer-123456" or "local-789"
|
||||
/// </summary>
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Artist { get; set; } = string.Empty;
|
||||
public string? ArtistId { get; set; }
|
||||
public string Album { get; set; } = string.Empty;
|
||||
public string? AlbumId { get; set; }
|
||||
public int? Duration { get; set; } // In seconds
|
||||
public int? Track { get; set; }
|
||||
public int? DiscNumber { get; set; }
|
||||
public int? TotalTracks { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public string? Genre { get; set; }
|
||||
public string? CoverArtUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// High-resolution cover art URL (for embedding)
|
||||
/// </summary>
|
||||
public string? CoverArtUrlLarge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// BPM (beats per minute) if available
|
||||
/// </summary>
|
||||
public int? Bpm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ISRC (International Standard Recording Code)
|
||||
/// </summary>
|
||||
public string? Isrc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Full release date (format: YYYY-MM-DD)
|
||||
/// </summary>
|
||||
public string? ReleaseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Album artist name (may differ from track artist)
|
||||
/// </summary>
|
||||
public string? AlbumArtist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Composer(s)
|
||||
/// </summary>
|
||||
public string? Composer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Album label
|
||||
/// </summary>
|
||||
public string? Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Copyright
|
||||
/// </summary>
|
||||
public string? Copyright { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contributing artists (features, etc.)
|
||||
/// </summary>
|
||||
public List<string> Contributors { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the song is available locally or needs to be downloaded
|
||||
/// </summary>
|
||||
public bool IsLocal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// External provider (deezer, spotify, etc.) - null if local
|
||||
/// </summary>
|
||||
public string? ExternalProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID on the external provider (for downloading)
|
||||
/// </summary>
|
||||
public string? ExternalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Local file path (if available)
|
||||
/// </summary>
|
||||
public string? LocalPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Deezer explicit content lyrics value
|
||||
/// 0 = Naturally clean, 1 = Explicit, 2 = Not applicable, 3 = Clean/edited version, 6/7 = Unknown
|
||||
/// </summary>
|
||||
public int? ExplicitContentLyrics { get; set; }
|
||||
}
|
||||
17
allstarr/Models/Download/DownloadInfo.cs
Normal file
17
allstarr/Models/Download/DownloadInfo.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace allstarr.Models.Download;
|
||||
|
||||
/// <summary>
|
||||
/// Information about an ongoing or completed download
|
||||
/// </summary>
|
||||
public class DownloadInfo
|
||||
{
|
||||
public string SongId { get; set; } = string.Empty;
|
||||
public string ExternalId { get; set; } = string.Empty;
|
||||
public string ExternalProvider { get; set; } = string.Empty;
|
||||
public DownloadStatus Status { get; set; }
|
||||
public double Progress { get; set; } // 0.0 to 1.0
|
||||
public string? LocalPath { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
public DateTime StartedAt { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
}
|
||||
12
allstarr/Models/Download/DownloadStatus.cs
Normal file
12
allstarr/Models/Download/DownloadStatus.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace allstarr.Models.Download;
|
||||
|
||||
/// <summary>
|
||||
/// Download status of a song
|
||||
/// </summary>
|
||||
public enum DownloadStatus
|
||||
{
|
||||
NotStarted,
|
||||
InProgress,
|
||||
Completed,
|
||||
Failed
|
||||
}
|
||||
13
allstarr/Models/Search/SearchResult.cs
Normal file
13
allstarr/Models/Search/SearchResult.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace allstarr.Models.Search;
|
||||
|
||||
using allstarr.Models.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Search result combining local and external results
|
||||
/// </summary>
|
||||
public class SearchResult
|
||||
{
|
||||
public List<Song> Songs { get; set; } = new();
|
||||
public List<Album> Albums { get; set; } = new();
|
||||
public List<Artist> Artists { get; set; } = new();
|
||||
}
|
||||
25
allstarr/Models/Settings/DeezerSettings.cs
Normal file
25
allstarr/Models/Settings/DeezerSettings.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace allstarr.Models.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for the Deezer downloader and metadata service
|
||||
/// </summary>
|
||||
public class DeezerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Deezer ARL token (required for downloading)
|
||||
/// Obtained from browser cookies after logging into deezer.com
|
||||
/// </summary>
|
||||
public string? Arl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fallback ARL token (optional)
|
||||
/// Used if the primary ARL token fails
|
||||
/// </summary>
|
||||
public string? ArlFallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Preferred audio quality: FLAC, MP3_320, MP3_128
|
||||
/// If not specified or unavailable, the highest available quality will be used.
|
||||
/// </summary>
|
||||
public string? Quality { get; set; }
|
||||
}
|
||||
67
allstarr/Models/Settings/JellyfinSettings.cs
Normal file
67
allstarr/Models/Settings/JellyfinSettings.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
namespace allstarr.Models.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for Jellyfin media server backend
|
||||
/// </summary>
|
||||
public class JellyfinSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// URL of the Jellyfin server
|
||||
/// Environment variable: JELLYFIN_URL
|
||||
/// </summary>
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// API key for authenticating with Jellyfin server
|
||||
/// Environment variable: JELLYFIN_API_KEY
|
||||
/// </summary>
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User ID for accessing Jellyfin library
|
||||
/// Environment variable: JELLYFIN_USER_ID
|
||||
/// </summary>
|
||||
public string? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Username that clients must provide to authenticate
|
||||
/// Environment variable: JELLYFIN_CLIENT_USERNAME
|
||||
/// </summary>
|
||||
public string? ClientUsername { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Music library ID in Jellyfin (optional, auto-detected if not specified)
|
||||
/// Environment variable: JELLYFIN_LIBRARY_ID
|
||||
/// </summary>
|
||||
public string? LibraryId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Client name reported to Jellyfin
|
||||
/// </summary>
|
||||
public string ClientName { get; set; } = "Allstarr";
|
||||
|
||||
/// <summary>
|
||||
/// Client version reported to Jellyfin
|
||||
/// </summary>
|
||||
public string ClientVersion { get; set; } = "1.0.0";
|
||||
|
||||
/// <summary>
|
||||
/// Device ID reported to Jellyfin
|
||||
/// </summary>
|
||||
public string DeviceId { get; set; } = "allstarrrr-proxy";
|
||||
|
||||
/// <summary>
|
||||
/// Device name reported to Jellyfin
|
||||
/// </summary>
|
||||
public string DeviceName { get; set; } = "Allstarr Proxy";
|
||||
|
||||
// Shared settings (same as SubsonicSettings)
|
||||
|
||||
public ExplicitFilter ExplicitFilter { get; set; } = ExplicitFilter.All;
|
||||
public DownloadMode DownloadMode { get; set; } = DownloadMode.Track;
|
||||
public MusicService MusicService { get; set; } = MusicService.SquidWTF;
|
||||
public StorageMode StorageMode { get; set; } = StorageMode.Permanent;
|
||||
public int CacheDurationHours { get; set; } = 1;
|
||||
public bool EnableExternalPlaylists { get; set; } = true;
|
||||
public string PlaylistsDirectory { get; set; } = "playlists";
|
||||
}
|
||||
25
allstarr/Models/Settings/QobuzSettings.cs
Normal file
25
allstarr/Models/Settings/QobuzSettings.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace allstarr.Models.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for the Qobuz downloader and metadata service
|
||||
/// </summary>
|
||||
public class QobuzSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Qobuz user authentication token
|
||||
/// Obtained from browser's localStorage after logging into play.qobuz.com
|
||||
/// </summary>
|
||||
public string? UserAuthToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Qobuz user ID
|
||||
/// Obtained from browser's localStorage after logging into play.qobuz.com
|
||||
/// </summary>
|
||||
public string? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Preferred audio quality: FLAC, MP3_320, MP3_128
|
||||
/// If not specified or unavailable, the highest available quality will be used.
|
||||
/// </summary>
|
||||
public string? Quality { get; set; }
|
||||
}
|
||||
7
allstarr/Models/Settings/RedisSettings.cs
Normal file
7
allstarr/Models/Settings/RedisSettings.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace allstarr.Models.Settings;
|
||||
|
||||
public class RedisSettings
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public string ConnectionString { get; set; } = "localhost:6379";
|
||||
}
|
||||
17
allstarr/Models/Settings/SquidWTFSettings.cs
Normal file
17
allstarr/Models/Settings/SquidWTFSettings.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace allstarr.Models.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for the SquidWTF downloader and metadata service
|
||||
/// </summary>
|
||||
public class SquidWTFSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// No user auth should be needed for this site.
|
||||
/// </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Preferred audio quality: FLAC, MP3_320, MP3_128
|
||||
/// If not specified or unavailable, the highest available quality will be used.
|
||||
/// </summary>
|
||||
public string? Quality { get; set; }
|
||||
}
|
||||
155
allstarr/Models/Settings/SubsonicSettings.cs
Normal file
155
allstarr/Models/Settings/SubsonicSettings.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
namespace allstarr.Models.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Media server backend type
|
||||
/// </summary>
|
||||
public enum BackendType
|
||||
{
|
||||
/// <summary>
|
||||
/// Subsonic-compatible server (Navidrome, Airsonic, etc.)
|
||||
/// </summary>
|
||||
Subsonic,
|
||||
|
||||
/// <summary>
|
||||
/// Jellyfin media server
|
||||
/// </summary>
|
||||
Jellyfin
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download mode for tracks
|
||||
/// </summary>
|
||||
public enum DownloadMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Download only the requested track (default behavior)
|
||||
/// </summary>
|
||||
Track,
|
||||
|
||||
/// <summary>
|
||||
/// When a track is played, download the entire album in background
|
||||
/// The requested track is downloaded first, then remaining tracks are queued
|
||||
/// </summary>
|
||||
Album
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicit content filter mode for Deezer tracks
|
||||
/// </summary>
|
||||
public enum ExplicitFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Show all tracks (no filtering)
|
||||
/// </summary>
|
||||
All,
|
||||
|
||||
/// <summary>
|
||||
/// Exclude clean/edited versions (explicit_content_lyrics == 3)
|
||||
/// Shows original explicit content and naturally clean content
|
||||
/// </summary>
|
||||
ExplicitOnly,
|
||||
|
||||
/// <summary>
|
||||
/// Only show clean content (explicit_content_lyrics == 0 or 3)
|
||||
/// Excludes tracks with explicit_content_lyrics == 1
|
||||
/// </summary>
|
||||
CleanOnly
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Storage mode for downloaded tracks
|
||||
/// </summary>
|
||||
public enum StorageMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Files are permanently stored in the library and registered in the database
|
||||
/// </summary>
|
||||
Permanent,
|
||||
|
||||
/// <summary>
|
||||
/// Files are stored in a temporary cache and automatically cleaned up
|
||||
/// Not registered in the database, no Navidrome scan triggered
|
||||
/// </summary>
|
||||
Cache
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Music service provider
|
||||
/// </summary>
|
||||
public enum MusicService
|
||||
{
|
||||
/// <summary>
|
||||
/// Deezer music service
|
||||
/// </summary>
|
||||
Deezer,
|
||||
|
||||
/// <summary>
|
||||
/// Qobuz music service
|
||||
/// </summary>
|
||||
Qobuz,
|
||||
|
||||
/// <summary>
|
||||
/// SquidWTF music service
|
||||
/// </summary>
|
||||
SquidWTF
|
||||
}
|
||||
|
||||
public class SubsonicSettings
|
||||
{
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Explicit content filter mode (default: All)
|
||||
/// Environment variable: EXPLICIT_FILTER
|
||||
/// Values: "All", "ExplicitOnly", "CleanOnly"
|
||||
/// Note: Only works with Deezer
|
||||
/// </summary>
|
||||
public ExplicitFilter ExplicitFilter { get; set; } = ExplicitFilter.All;
|
||||
|
||||
/// <summary>
|
||||
/// Download mode for tracks (default: Track)
|
||||
/// Environment variable: DOWNLOAD_MODE
|
||||
/// Values: "Track" (download only played track), "Album" (download full album when playing a track)
|
||||
/// </summary>
|
||||
public DownloadMode DownloadMode { get; set; } = DownloadMode.Track;
|
||||
|
||||
/// <summary>
|
||||
/// Music service to use (default: Deezer)
|
||||
/// Environment variable: MUSIC_SERVICE
|
||||
/// Values: "Deezer", "Qobuz", "SquidWTF"
|
||||
/// </summary>
|
||||
|
||||
public MusicService MusicService { get; set; } = MusicService.SquidWTF;
|
||||
|
||||
/// <summary>
|
||||
/// Storage mode for downloaded files (default: Permanent)
|
||||
/// Environment variable: STORAGE_MODE
|
||||
/// Values: "Permanent" (files saved to library), "Cache" (temporary files, auto-cleanup)
|
||||
/// </summary>
|
||||
public StorageMode StorageMode { get; set; } = StorageMode.Permanent;
|
||||
|
||||
/// <summary>
|
||||
/// Cache duration in hours for Cache storage mode (default: 1)
|
||||
/// Environment variable: CACHE_DURATION_HOURS
|
||||
/// Files older than this duration will be automatically deleted
|
||||
/// Only applies when StorageMode is Cache
|
||||
/// </summary>
|
||||
public int CacheDurationHours { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Enable external playlist search and streaming (default: true)
|
||||
/// Environment variable: ENABLE_EXTERNAL_PLAYLISTS
|
||||
/// When enabled, users can search for playlists from the configured music provider
|
||||
/// Playlists appear as "albums" in search results with genre "Playlist"
|
||||
/// </summary>
|
||||
public bool EnableExternalPlaylists { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Directory name for storing playlist .m3u files (default: "playlists")
|
||||
/// Environment variable: PLAYLISTS_DIRECTORY
|
||||
/// Relative to the music library root directory
|
||||
/// Playlist files will be stored in {MusicDirectory}/{PlaylistsDirectory}/
|
||||
/// </summary>
|
||||
public string PlaylistsDirectory { get; set; } = "playlists";
|
||||
|
||||
}
|
||||
58
allstarr/Models/Subsonic/ExternalPlaylist.cs
Normal file
58
allstarr/Models/Subsonic/ExternalPlaylist.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
namespace allstarr.Models.Subsonic;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a playlist from an external music provider (Deezer, Qobuz).
|
||||
/// </summary>
|
||||
public class ExternalPlaylist
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier in the format "pl-{provider}-{externalId}"
|
||||
/// Example: "pl-deezer-123456" or "pl-qobuz-789"
|
||||
/// </summary>
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Playlist name
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Playlist description
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the playlist creator/curator
|
||||
/// </summary>
|
||||
public string? CuratorName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Provider name ("deezer" or "qobuz")
|
||||
/// </summary>
|
||||
public string Provider { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// External ID from the provider (without "pl-" prefix)
|
||||
/// </summary>
|
||||
public string ExternalId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Number of tracks in the playlist
|
||||
/// </summary>
|
||||
public int TrackCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total duration in seconds
|
||||
/// </summary>
|
||||
public int Duration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cover art URL from the provider
|
||||
/// </summary>
|
||||
public string? CoverUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Playlist creation date
|
||||
/// </summary>
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
}
|
||||
10
allstarr/Models/Subsonic/ScanStatus.cs
Normal file
10
allstarr/Models/Subsonic/ScanStatus.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace allstarr.Models.Subsonic;
|
||||
|
||||
/// <summary>
|
||||
/// Subsonic library scan status
|
||||
/// </summary>
|
||||
public class ScanStatus
|
||||
{
|
||||
public bool Scanning { get; set; }
|
||||
public int? Count { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user