diff --git a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs
index 3aa0f0408b..c1ccb24bf4 100644
--- a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs
@@ -684,27 +684,37 @@ namespace Jellyfin.LiveTv.Listings
sdCode?.ToString() ?? "N/A",
responseBody);
- if (sdCode is SdErrorCode.InvalidUser or SdErrorCode.InvalidHash or SdErrorCode.AccountLocked or SdErrorCode.AccountExpired or SdErrorCode.PasswordRequired)
+ if (sdCode is SdErrorCode.AccountExpired or SdErrorCode.InvalidHash or SdErrorCode.InvalidUser or SdErrorCode.AccountLocked or SdErrorCode.AppLocked or SdErrorCode.AccountInactive)
{
// Permanent account errors — disable SD for this server lifetime.
- _logger.LogError("Schedules Direct account error (code {SdCode}). Disabling SD until server restart", sdCode);
+ _logger.LogError("Schedules Direct account error (code {SdCode}). Disabling SD until server restart.", sdCode);
_tokens.Clear();
_accountError = true;
}
- else if (sdCode is SdErrorCode.MaxLoginAttempts or SdErrorCode.TemporaryLockout)
+ else if (sdCode is SdErrorCode.ServiceOffline or SdErrorCode.ServiceBusy or SdErrorCode.AccountTempLock)
{
// Transient login errors — back off for 30 minutes, then allow retry.
+ _logger.LogError("Schedules Direct transient error (code {SdCode}). Backing off for 30 minutes.", sdCode);
_tokens.Clear();
Interlocked.Exchange(ref _lastErrorResponseTicks, DateTime.UtcNow.Ticks);
}
- else if (sdCode is SdErrorCode.MaxImageDownloads)
+ else if (sdCode is SdErrorCode.MaxLoginAttempts or SdErrorCode.MaxIPAttempts)
+ {
+ // 24 hour bans - stop image and metadata requests until SD reset at 00:00 UTC.
+ _logger.LogError("Schedules Direct service limit error (code {SdCode}). Disabling until SD reset.", sdCode);
+ SetImageLimitHit();
+ SetMetadataLimitHit();
+ }
+ else if (sdCode is SdErrorCode.MaxImageDownloads or SdErrorCode.MaxImageDownloadsTrial)
{
// Max image downloads — stop image requests until SD resets at 00:00 UTC.
+ _logger.LogError("Schedules Direct image download limit hit (code {SdCode}). Disabling image acquisition until SD reset.", sdCode);
SetImageLimitHit();
}
else if (sdCode is SdErrorCode.MaxScheduleRequests)
{
// Max schedule/metadata requests — stop metadata requests until SD resets at 00:00 UTC.
+ _logger.LogError("Schedules Direct metadata download limit hit (code {SdCode}). Disabling metadata acquisition until SD reset.", sdCode);
SetMetadataLimitHit();
}
else if (enableRetry
diff --git a/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/SdErrorCode.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/SdErrorCode.cs
index ec6c6c475b..fffbfb9a58 100644
--- a/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/SdErrorCode.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/SdErrorCode.cs
@@ -3,39 +3,59 @@
namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos;
///
-/// Schedules Direct API error codes.
+/// Schedules Direct API error codes. See https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#error-response for details.
///
public enum SdErrorCode
{
///
- /// Invalid user.
+ /// Schedules Direct unavailable/out of service.
///
- InvalidUser = 4001,
+ ServiceOffline = 3000,
///
- /// Invalid password hash.
+ /// Schedules Direct busy.
///
- InvalidHash = 4003,
-
- ///
- /// Account locked or disabled.
- ///
- AccountLocked = 4004,
+ ServiceBusy = 3001,
///
/// Account expired.
///
- AccountExpired = 4005,
+ AccountExpired = 4001,
///
- /// Token has expired.
+ /// Invalid password hash.
+ ///
+ InvalidHash = 4002,
+
+ ///
+ /// Invalid user or password.
+ ///
+ InvalidUser = 4003,
+
+ ///
+ /// Account temporarily locked due to login failures.
+ ///
+ AccountTempLock = 4004,
+
+ ///
+ /// Account permanently locked due to abuse.
+ ///
+ AccountLocked = 4005,
+
+ ///
+ /// Token has expired. Request a new one.
///
TokenExpired = 4006,
///
- /// Password is required.
+ /// Application locked out.
///
- PasswordRequired = 4008,
+ AppLocked = 4007,
+
+ ///
+ /// Account not active.
+ ///
+ AccountInactive = 4008,
///
/// Maximum login attempts exceeded.
@@ -43,17 +63,32 @@ public enum SdErrorCode
MaxLoginAttempts = 4009,
///
- /// Temporary lockout.
+ /// Maximum unique IP attempts reached.
///
- TemporaryLockout = 4010,
+ MaxIPAttempts = 4010,
+
+ ///
+ /// Lineup change maximum reached.
+ ///
+ MaxScheduleRequests = 4100,
+
+ ///
+ /// Requested image not found.
+ ///
+ ImageNotFound = 5000,
///
/// Maximum image downloads reached for the day.
///
MaxImageDownloads = 5002,
+ ///
+ /// Trial specific maximum image downloads reached for the day.
+ ///
+ MaxImageDownloadsTrial = 5003,
+
///
/// Maximum schedule/metadata requests reached for the day.
///
- MaxScheduleRequests = 5003
+ MaxInvalidImages = 5004
}