using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using System.Reflection; using System.Security.Cryptography; using Newtonsoft.Json; using System.IO; using System.Threading; namespace JellyfinRPC { class JellyfinAPI { public static async Task Jellyfin() { while (true) { try { var playingInfo = await GetCurrentlyPlaying(); if (playingInfo != null) { JToken nowPlaying = playingInfo.NowPlayingItem; string largeImageKey = playingInfo.IsMusic ? GetAlbumCover(nowPlaying) : await GetJellyfinLogo(); string largeImageText = playingInfo.IsMusic ? nowPlaying["Album"]?.ToString() ?? "Unknown Album" : "Jellyfin"; string details = playingInfo.IsMusic ? $"{playingInfo.Title}" : nowPlaying["SeriesName"] != null ? $"Watching {nowPlaying["SeriesName"]} - {playingInfo.Title}" : $"Watching {playingInfo.Title}"; string state = playingInfo.IsMusic ? $"{playingInfo.Artist}" : nowPlaying["SeriesName"] != null ? $"Season {playingInfo.Season}, Episode {playingInfo.Episode}" : ""; DateTime duration = playingInfo.Duration; DateTime progress = playingInfo.Progress; return $"{largeImageKey}|{largeImageText}|{details}|{state}|{duration}|{progress}"; } } catch (Exception ex) { ConsoleManager.WriteToConsole($"Error fetching Jellyfin data: {ex.Message}"); } } } public static string AssemblyVersion { get { return Assembly.GetExecutingAssembly().GetName().Version.ToString(); } } private static async Task GetJellyfinLogo() { using var httpClient = new HttpClient(); try { if (ConfigManager.GetEntry("JellyfinToken") == "") { var response = await httpClient.GetAsync($"{ConfigManager.GetEntry("ServerURL")}/System/Info?api_key={ConfigManager.GetEntry("APIKey")}"); response.EnsureSuccessStatusCode(); var jsonResponse = await response.Content.ReadAsStringAsync(); var json = JArray.Parse(jsonResponse); var logoUrl = json["LogoUrl"]?.ToString(); return logoUrl ?? "jellyfin_logo"; } else { return "jellyfin_logo"; } } catch (Exception ex) { ConsoleManager.WriteToConsole($"Error fetching Jellyfin logo: {ex.Message}"); return "jellyfin_logo"; } } private static async Task GetCurrentlyPlaying() { using var httpClient = new HttpClient(); try { if (ConfigManager.GetEntry("JellyfinToken") == "") { var playingInfoResponse = await httpClient.GetAsync($"{ConfigManager.GetEntry("ServerURL")}/Sessions?api_key={ConfigManager.GetEntry("APIKey")}"); playingInfoResponse.EnsureSuccessStatusCode(); var jsonResponse = await playingInfoResponse.Content.ReadAsStringAsync(); var sessions = JArray.Parse(jsonResponse); foreach (var session in sessions) { if (session["UserId"]?.ToString() == ConfigManager.GetEntry("UserID") && session["NowPlayingItem"] != null) { var nowPlaying = session["NowPlayingItem"]; var mediaType = nowPlaying["Type"]?.ToString(); bool isMusic = mediaType?.ToLower() == "audio"; string albumCover = ""; string artist = "Unknown Artist"; if (isMusic) { albumCover = GetAlbumCover(nowPlaying); var artists = nowPlaying["Artists"]?.ToObject(); if (artists != null && artists.Count > 0) { artist = artists[0].ToString(); } else { artist = "Unknown Artist"; } } return new PlayingInfo { Title = nowPlaying["Name"]?.ToString(), Artist = artist, AlbumCover = albumCover, Season = nowPlaying["ParentIndexNumber"]?.ToString() ?? "N/A", Episode = nowPlaying["IndexNumber"]?.ToString() ?? "N/A", Progress = new DateTime((long)session["PlayState"]["PositionTicks"]), //Progress = TimeSpan.FromTicks((long)session["PlayState"]["PositionTicks"]), Duration = new DateTime((long)nowPlaying["RunTimeTicks"]), //Duration = TimeSpan.FromTicks((long)nowPlaying["RunTimeTicks"]), IsMusic = isMusic, NowPlayingItem = nowPlaying }; } } } } catch (Exception ex) { ConsoleManager.WriteToConsole($"Error fetching Jellyfin data: {ex.Message}"); } return null; } private static string GetAlbumCover(JToken nowPlaying) { var mediaStreams = nowPlaying["MediaStreams"]?.ToObject(); if (mediaStreams != null) { foreach (var stream in mediaStreams) { var imageTag = stream["ImageTag"]?.ToString(); if (!string.IsNullOrEmpty(imageTag)) { return imageTag; } } } return "album_cover"; } public static string quickConnectToken; public static bool formClosed; public static bool authSuccess; public static async Task AuthWithQuickConnect() { using var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Add("Authorization", $"MediaBrowser Client=\"JellyfinRPC {AssemblyVersion}, \", Device=\"{System.Net.Dns.GetHostName()}\", DeviceId=\"{ConfigManager.GetEntry("DeviceID")}\", Version=\"running on {Environment.OSVersion}\""); if (ConfigManager.GetEntry("ServerURL") != "") { var quickConnectInitResponse = await httpClient.GetAsync($"{ConfigManager.GetEntry("ServerURL")}/QuickConnect/Initiate"); authSuccess = false; if (quickConnectInitResponse.StatusCode is (System.Net.HttpStatusCode)401) { System.Windows.Forms.MessageBox.Show("This server does not have Quick Connect enabled.", "Quick Connect Unavailable.", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error, System.Windows.Forms.MessageBoxDefaultButton.Button1); } else { try { quickConnectInitResponse.EnsureSuccessStatusCode(); var jsonResponse = await quickConnectInitResponse.Content.ReadAsStringAsync(); var jsonObject = JObject.Parse(jsonResponse); if (!jsonObject.HasValues) { } else { string quickConnectCode = jsonObject.Value("Code"); if (jsonObject.Value("Secret") != null && jsonObject.Value("Secret") != "") { quickConnectToken = jsonObject.Value("Secret"); } else { quickConnectToken = null; } (System.Windows.Forms.Application.OpenForms["ConfigForm"] as ConfigForm).label9.Text = quickConnectCode; while (authSuccess == false && formClosed == false) { await CheckQuickConnectStatus(); } } } catch (Exception ex) { #if DEBUG System.Windows.Forms.MessageBox.Show(ex.ToString()); #endif } } } else { System.Windows.Forms.MessageBox.Show("No Server URL is set.", "Quick Connect Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Exclamation); } } public static string DeviceID() { using RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); byte[] randomNumber = new byte[4]; rng.GetBytes(randomNumber); int value = BitConverter.ToInt32(randomNumber, 0); byte[] valueBytes = System.Text.Encoding.UTF8.GetBytes(value.ToString()); return System.Convert.ToBase64String(valueBytes); } public static async Task GetTokenFromUsernameAndPassword(string Username, string Password) { using var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Add("Authorization", $"MediaBrowser Client=\"JellyfinRPC {AssemblyVersion}, \", Device=\"{System.Net.Dns.GetHostName()}\", DeviceId=\"{ConfigManager.GetEntry("DeviceID")}\", Version=\"running on {Environment.OSVersion}\""); if (ConfigManager.GetEntry("ServerURL") != "") { string username = Username; string password = Password; var loginRequest = new Login() { username = username, pw = password }; var loginJson = JsonConvert.SerializeObject(loginRequest); HttpResponseMessage loginWithUsernameandPassResponse = await httpClient.PostAsync($"{ConfigManager.GetEntry("ServerURL")}/Users/AuthenticateByName", new StringContent(loginJson, Encoding.UTF8, "application/json")); if (loginWithUsernameandPassResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized) { System.Windows.Forms.MessageBox.Show("The Username or Password is incorrect.", "Authentication Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error, System.Windows.Forms.MessageBoxDefaultButton.Button1); } else { loginWithUsernameandPassResponse.EnsureSuccessStatusCode(); var jsonResponse = await loginWithUsernameandPassResponse.Content.ReadAsStringAsync(); var jsonShit = JObject.Parse(jsonResponse); string thatToken = jsonShit.Value("AccessToken"); if (thatToken != null && thatToken != " ") { ConfigManager.SetEntry("JellyfinToken", thatToken); } } } } public static async Task CheckQuickConnectStatus() { using var httpClient = new HttpClient(); if (quickConnectToken != null && quickConnectToken != "") { TaskEx.Delay(1000); string secret = quickConnectToken.Replace('"', ' ').Trim(); HttpResponseMessage QuickConnectStatusResponse = await httpClient.GetAsync($"{ConfigManager.GetEntry("ServerURL")}/QuickConnect/Connect?Secret={quickConnectToken}"); QuickConnectStatusResponse.EnsureSuccessStatusCode(); var responseAsString = await QuickConnectStatusResponse.Content.ReadAsStringAsync(); var responseJson = JObject.Parse(responseAsString); if (responseJson.Value("Authenticated") == true) { var quickConnectLoginSecret = new JellyfinAuth() { AccessToken = quickConnectToken }; var quickConnectLoginJson = JsonConvert.SerializeObject(quickConnectLoginSecret); HttpResponseMessage quickConnectLoginResponse = await httpClient.PostAsync($"{ConfigManager.GetEntry("ServerURL")}/Users/AuthenticateWithQuickConnect", new StringContent(quickConnectLoginJson, Encoding.UTF8, "application/json")); quickConnectLoginResponse.EnsureSuccessStatusCode(); var quickConnectLoginResponseAsString = await quickConnectLoginResponse.Content.ReadAsStringAsync(); var quickConnectLoginResponseJson = JObject.Parse(quickConnectLoginResponseAsString); if (quickConnectLoginResponseJson.HasValues) { if (quickConnectLoginResponseJson.Value("AccessToken") != null && quickConnectLoginResponseJson.Value("AccessToken") != "") { ConfigManager.SetEntry("JellyfinToken", quickConnectLoginResponseJson.Value("AccessToken")); authSuccess = true; (System.Windows.Forms.Application.OpenForms["ConfigForm"] as ConfigForm).label9.Text = "Authentication Successful."; } else { throw new Exception("The server did not return an access token, this shouldn't happen."); } } else { System.Windows.Forms.MessageBox.Show("The server didn't respond for authentication.", "Authentication Error.", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Exclamation); } } else { authSuccess = false; } } } } // don't touch! #region classes public class Login { #pragma warning disable IDE1006 // Naming Styles public string username { get; set; } public string pw { get; set; } #pragma warning restore IDE1006 // Naming Styles } public class JellyfinAuth { public User User { get; } public string AccessToken { get; set; } public string ServerId { get; } } public class User { public string Name { get; } public string ServerId { get; } public string Id { get; } public string PrimaryImageTag { get; } public bool HasPassword { get; } public bool HasConfiguredPassword { get; } public bool HasConfiguredEasyPassword { get; } public bool EnableAutoLogin { get; } public string LastLoginDate { get; } public string LastActivityDate { get; } public Configuration Configuration { get; } public Policy Policy { get; } public int PrimaryImageAspectRatio { get; } } public class SessionInfo { public JArray AdditionalUsers { get; } public string RemoteEndPoint { get; } public JArray PlayableMediaTypes { get; } public string Id { get; } public string UserId { get; } public string UserName { get; } public string Client { get; } public string LastActivityDate { get; } public string LastPlaybackCheckIn { get; } public string LastPausedDate { get; } public string DeviceName { get; } public string DeviceType { get; } public string DeviceId { get; } public string ApplicationVersion { get; } public bool IsActive { get; } public bool SuportsMediaControl { get; } public bool SupportsRemoteControl { get; } public JArray NowPlayingQueue { get; } public JArray NowPlayingQueueFullItems { get; } public bool HasCustomDeviceName { get; } public string PlaylistItemId { get; } public string ServerId { get; } public string UserPrimaryImageTag { get; } public JArray SupportedCommands { get; } } public class Configuration { public string AudioLanguagePreference { get; } public bool PlayDefaultAudioTrack { get; } public string SubtitleLanguagePreference { get; } public bool DisplayMissingEpisodes { get; } public JArray GroupedFolders { get; } public string SubtitleMode { get; } public bool DisplayCollectionsView { get; } public bool EnableLocalPassword { get; } public JArray OrderedViews { get; } public JArray LatestItemsExcludes { get; } public JArray MyMediaExcludes { get; } public bool HidePlayedInLatest { get; } public bool RememberSubtitleSelections { get; } public bool EnableNextEpisodeAutoPlay { get; } public string CastRecieverId { get; } } public class Policy { // we don't actualy care about the policies, but i'm defining them in here just in case public bool IsAdministrator { get; } public bool IsHidden { get; } public bool EnableCollectionManagement { get; } public bool EnableSubtitleManagement { get; } public bool EnableLyricManagement { get; } public bool IsDisabled { get; } public int MaxParentalRating { get; } public JArray BlockedTags { get; } public JArray AllowedTags { get; } public bool EnableUserPreferenceAccess { get; } public JArray AccessSchedules { get; } public JArray BlockUnratedItems { get; } public bool EnableRemoteControlOfOtherUsers { get; } public bool EnableSharedDeviceControl { get; } public bool EnableRemoteAccess { get; } public bool EnableLiveTvManagement { get; } public bool EnableLiveTvAccess { get; } public bool EnableMediaPlayback { get; } public bool EnableAudioPlaybackTranscoding { get; } public bool EnableVideoPlaybackTranscoding { get; } public bool EnablePlaybackRemuxing { get; } public bool ForceRemoteSourceTranscoding { get; } public bool EnableConetentDeletion { get; } public JArray EnableContentDeletionFromFolders { get; } public bool EnableContentDownloading { get; } public bool EnableSyncTranscoding { get; } public bool EnableMediaConverison { get; } public JArray EnabledDevices { get; } public bool EnableAllDevices { get; } public JArray EnabledChannels { get; } public bool EnableAllChannels { get; } public JArray EnabledFolders { get; } public bool EnableAllFolders { get; } public int InvalidLoginAttemptCount { get; } public int LoginAttemptsBeforeLockout { get; } public int MaxActiveSessions { get; } public bool EnablePublicSharing { get; } public JArray BlockedMediaFolders { get; } public JArray BlockedChannels { get; } public int RemoteClientBitrateLimit { get; } public string AuthenticationProviderId { get; } public string PasswordResetProviderId { get; } public string SyncPlayAccess { get; } } #endregion }