Upload project
This commit is contained in:
commit
cf4086a310
33 changed files with 998 additions and 0 deletions
272
JellyfinDiscordRPC/Program.cs
Normal file
272
JellyfinDiscordRPC/Program.cs
Normal file
|
@ -0,0 +1,272 @@
|
|||
using DiscordRPC;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
class Program
|
||||
{
|
||||
private const string ConfigFilePath = "config.json";
|
||||
private static DiscordRpcClient _discordClient;
|
||||
private static Config _config;
|
||||
|
||||
// Hard-code the Discord Client ID here
|
||||
private const string DiscordClientId = "1312264302601834578"; // Replace with your actual Discord client ID
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Starting Jellyfin Discord Rich Presence...");
|
||||
|
||||
// Load or create configuration
|
||||
LoadOrCreateConfig();
|
||||
|
||||
// Initialize Discord Rich Presence
|
||||
_discordClient = new DiscordRpcClient(DiscordClientId);
|
||||
_discordClient.OnReady += (sender, e) => Console.WriteLine("Connected to Discord RPC!");
|
||||
_discordClient.OnPresenceUpdate += (sender, e) => Console.WriteLine("Rich Presence updated!");
|
||||
_discordClient.Initialize();
|
||||
|
||||
// Poll Jellyfin API for currently playing media
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var playingInfo = await GetCurrentlyPlaying().ConfigureAwait(false);
|
||||
if (playingInfo != null)
|
||||
{
|
||||
// Get the raw JToken from nowPlaying (instead of passing PlayingInfo)
|
||||
JToken nowPlaying = playingInfo.NowPlayingItem;
|
||||
|
||||
// Check if album art exists and pre-fetch it
|
||||
string largeImageKey = playingInfo.IsMusic
|
||||
? await GetAlbumCover(nowPlaying).ConfigureAwait(false) // Pass the JToken nowPlaying here
|
||||
: await GetJellyfinLogo().ConfigureAwait(false);
|
||||
string largeImageText = playingInfo.IsMusic
|
||||
? nowPlaying["Album"]?.ToString() ?? "Unknown Album" // Extract album name or fallback to "Unknown Album"
|
||||
: "Jellyfin Media Player";
|
||||
// Update Discord Rich Presence for music or video
|
||||
_discordClient.SetPresence(new RichPresence
|
||||
{
|
||||
Details = playingInfo.IsMusic
|
||||
? $"{playingInfo.Title}" // Show song name
|
||||
: $"Watching: {playingInfo.Title}", // Show video title
|
||||
State = playingInfo.IsMusic
|
||||
? $"{playingInfo.Artist}" // Show artist only (no album name)
|
||||
: $"Season {playingInfo.Season}, Episode {playingInfo.Episode}",
|
||||
Timestamps = new Timestamps
|
||||
{
|
||||
Start = DateTime.UtcNow - playingInfo.Progress, // Start time based on current progress
|
||||
End = DateTime.UtcNow + (playingInfo.Duration - playingInfo.Progress) // End time based on total duration
|
||||
},
|
||||
Assets = new Assets
|
||||
{
|
||||
LargeImageKey = largeImageKey, // Dynamically set image based on media type
|
||||
LargeImageText = largeImageText // Use album name or fallback text
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_discordClient.ClearPresence();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex.Message}");
|
||||
}
|
||||
|
||||
// Wait for a few seconds before polling again
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> GetJellyfinLogo()
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
try
|
||||
{
|
||||
var response = await httpClient.GetAsync($"{_config.JellyfinBaseUrl}/System/Info?api_key={_config.JellyfinApiKey}").ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
var json = JObject.Parse(jsonResponse);
|
||||
|
||||
// Attempt to get the logo URL
|
||||
var logoUrl = json["LogoUrl"]?.ToString();
|
||||
|
||||
// Fallback to a default logo if no logo URL is found
|
||||
return logoUrl ?? "jellyfin_logo";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error fetching Jellyfin logo: {ex.Message}");
|
||||
return "jellyfin_logo"; // Default fallback in case of error
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadOrCreateConfig()
|
||||
{
|
||||
if (File.Exists(ConfigFilePath))
|
||||
{
|
||||
// Load configuration from file
|
||||
var configJson = File.ReadAllText(ConfigFilePath);
|
||||
_config = JsonSerializer.Deserialize<Config>(configJson);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prompt user for configuration on first startup (without Discord Client ID)
|
||||
_config = new Config();
|
||||
|
||||
do
|
||||
{
|
||||
Console.Write("Enter Jellyfin Server URL (e.g., http://your-jellyfin-server:8096): ");
|
||||
_config.JellyfinBaseUrl = Console.ReadLine();
|
||||
} while (string.IsNullOrWhiteSpace(_config.JellyfinBaseUrl));
|
||||
|
||||
do
|
||||
{
|
||||
Console.Write("Enter Jellyfin API Key: ");
|
||||
_config.JellyfinApiKey = Console.ReadLine();
|
||||
} while (string.IsNullOrWhiteSpace(_config.JellyfinApiKey));
|
||||
|
||||
Console.WriteLine("Go to the following URL and find your Jellyfin User ID:");
|
||||
Console.WriteLine(" http://<your-jellyfin-server>:PORTNUMBER/Users?api_key=<your-api-key>");
|
||||
do
|
||||
{
|
||||
Console.Write("Enter Jellyfin User ID: ");
|
||||
_config.JellyfinUserId = Console.ReadLine();
|
||||
} while (string.IsNullOrWhiteSpace(_config.JellyfinUserId));
|
||||
|
||||
// Save configuration to file (without Discord Client ID)
|
||||
var configJson = JsonSerializer.Serialize(_config, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(ConfigFilePath, configJson);
|
||||
|
||||
Console.WriteLine("Configuration saved to config.json.");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<PlayingInfo?> GetCurrentlyPlaying()
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
try
|
||||
{
|
||||
var response = await httpClient.GetAsync($"{_config.JellyfinBaseUrl}/Sessions?api_key={_config.JellyfinApiKey}").ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
var sessions = JArray.Parse(jsonResponse);
|
||||
|
||||
foreach (var session in sessions)
|
||||
{
|
||||
if (session["UserId"]?.ToString() == _config.JellyfinUserId &&
|
||||
session["NowPlayingItem"] != null)
|
||||
{
|
||||
var nowPlaying = session["NowPlayingItem"]; // This is the raw JToken we need
|
||||
|
||||
// Extract media type
|
||||
var mediaType = nowPlaying["Type"]?.ToString();
|
||||
bool isMusic = mediaType?.ToLower() == "audio";
|
||||
|
||||
string albumCover = ""; // Default value for album cover
|
||||
string artist = "Unknown Artist"; // Default value for artist
|
||||
|
||||
// If it's music, extract the album art and artist name
|
||||
if (isMusic)
|
||||
{
|
||||
albumCover = await GetAlbumCover(nowPlaying).ConfigureAwait(false); // Pass JToken (not PlayingInfo)
|
||||
var artists = nowPlaying["Artists"]?.ToObject<JArray>();
|
||||
if (artists != null && artists.Count > 0)
|
||||
{
|
||||
artist = artists[0].ToString(); // Get the first artist from the array
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it's video, set artist to Unknown Artist
|
||||
artist = "Unknown Artist";
|
||||
}
|
||||
|
||||
// Return the extracted data wrapped in the PlayingInfo object
|
||||
return new PlayingInfo
|
||||
{
|
||||
Title = nowPlaying["Name"]?.ToString(),
|
||||
Artist = artist,
|
||||
AlbumCover = albumCover,
|
||||
Season = nowPlaying["ParentIndexNumber"]?.ToString() ?? "N/A",
|
||||
Episode = nowPlaying["IndexNumber"]?.ToString() ?? "N/A",
|
||||
Progress = TimeSpan.FromTicks((long)session["PlayState"]["PositionTicks"]),
|
||||
Duration = TimeSpan.FromTicks((long)nowPlaying["RunTimeTicks"]),
|
||||
IsMusic = isMusic,
|
||||
NowPlayingItem = nowPlaying // Add the raw NowPlayingItem JToken here
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error fetching Jellyfin data: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static async Task<string> GetAlbumCover(JToken nowPlaying)
|
||||
{
|
||||
// Try to get the album cover from the "MediaStreams" (audio specific)
|
||||
var mediaStreams = nowPlaying["MediaStreams"]?.ToObject<JArray>();
|
||||
if (mediaStreams != null)
|
||||
{
|
||||
// Look for the stream that indicates an image (usually "mjpeg" codec)
|
||||
foreach (var stream in mediaStreams)
|
||||
{
|
||||
if (stream["Codec"]?.ToString() == "mjpeg" && stream["Type"]?.ToString() == "EmbeddedImage")
|
||||
{
|
||||
// Check for the image tag and try to form the image URL
|
||||
string imageTag = nowPlaying["ParentLogoImageTag"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(imageTag))
|
||||
{
|
||||
string itemId = nowPlaying["Id"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(itemId))
|
||||
{
|
||||
// Construct the URL for the album cover
|
||||
return $"{_config.JellyfinBaseUrl}/emby/Items/{itemId}/Images/Primary?tag={imageTag}&api_key={_config.JellyfinApiKey}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Check if there is an explicit album cover URL in the item
|
||||
string albumArtUrl = nowPlaying["ImageTags"]?["Primary"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(albumArtUrl))
|
||||
{
|
||||
return $"{_config.JellyfinBaseUrl}/emby/Items/{nowPlaying["Id"]}/Images/Primary?tag={albumArtUrl}&api_key={_config.JellyfinApiKey}";
|
||||
}
|
||||
|
||||
// Fallback to a default cover if no album art is found
|
||||
return "default_cover";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Config
|
||||
{
|
||||
public string JellyfinBaseUrl { get; set; }
|
||||
public string JellyfinApiKey { get; set; }
|
||||
public string JellyfinUserId { get; set; }
|
||||
}
|
||||
|
||||
class PlayingInfo
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Artist { get; set; }
|
||||
public string AlbumCover { get; set; }
|
||||
public string Season { get; set; }
|
||||
public string Episode { get; set; }
|
||||
public TimeSpan Progress { get; set; }
|
||||
public TimeSpan Duration { get; set; }
|
||||
public bool IsMusic { get; set; }
|
||||
public JToken NowPlayingItem { get; set; }
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue