trakt list sorting order fix
This commit is contained in:
parent
ff1b406c48
commit
71487fce59
9 changed files with 253 additions and 141 deletions
|
|
@ -467,7 +467,7 @@
|
|||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||
PRODUCT_NAME = "Nuvio";
|
||||
PRODUCT_NAME = Nuvio;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
@ -501,7 +501,7 @@
|
|||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||
PRODUCT_NAME = "Nuvio";
|
||||
PRODUCT_NAME = Nuvio;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
|
|
|||
|
|
@ -1,99 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Nuvio</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>nuvio</string>
|
||||
<string>com.nuvio.app</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>exp+nuvio</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>18</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_http._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your local network</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app does not require microphone access.</string>
|
||||
<key>RCTRootViewBackgroundColor</key>
|
||||
<integer>4278322180</integer>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<false/>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleDefault</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Dark</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Nuvio</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>nuvio</string>
|
||||
<string>com.nuvio.app</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>exp+nuvio</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>18</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_http._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your local network</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app does not require microphone access.</string>
|
||||
<key>RCTRootViewBackgroundColor</key>
|
||||
<integer>4278322180</integer>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<false/>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleDefault</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Dark</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
<dict/>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ PODS:
|
|||
- ExpoModulesCore
|
||||
- ExpoDevice (7.0.3):
|
||||
- ExpoModulesCore
|
||||
- ExpoDocumentPicker (14.0.7):
|
||||
- ExpoDocumentPicker (13.0.3):
|
||||
- ExpoModulesCore
|
||||
- ExpoFileSystem (18.0.12):
|
||||
- ExpoModulesCore
|
||||
|
|
@ -248,7 +248,7 @@ PODS:
|
|||
- SDWebImageSVGCoder (~> 1.7.0)
|
||||
- ExpoKeepAwake (14.0.3):
|
||||
- ExpoModulesCore
|
||||
- ExpoLibVlcPlayer (2.1.7):
|
||||
- ExpoLibVlcPlayer (2.2.1):
|
||||
- ExpoModulesCore
|
||||
- MobileVLCKit (= 3.6.1b1)
|
||||
- ExpoLinearGradient (14.0.2):
|
||||
|
|
@ -304,7 +304,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- ExpoSharing (14.0.7):
|
||||
- ExpoSharing (13.0.1):
|
||||
- ExpoModulesCore
|
||||
- ExpoSystemUI (4.0.9):
|
||||
- ExpoModulesCore
|
||||
|
|
@ -2908,20 +2908,20 @@ SPEC CHECKSUMS:
|
|||
ExpoBrightness: c0011699a3225c869666e266326774a6fb6a9075
|
||||
ExpoCrypto: e97e864c8d7b9ce4a000bca45dddb93544a1b2b4
|
||||
ExpoDevice: d36ab4186b6799a28fd449bb9a1c77455f23fd1a
|
||||
ExpoDocumentPicker: 2200eefc2817f19315fa18f0147e0b80ece86926
|
||||
ExpoDocumentPicker: 6d3d499cf15b692688a804f42927d0f35de5ebaa
|
||||
ExpoFileSystem: 42d363d3b96f9afab980dcef60d5657a4443c655
|
||||
ExpoFont: f354e926f8feae5e831ec8087f36652b44a0b188
|
||||
ExpoHaptics: 8d199b2f33245ea85289ff6c954c7ee7c00a5b5d
|
||||
ExpoImage: d840b256050f4428d2942bc2b6e9251f9e0d7021
|
||||
ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680
|
||||
ExpoLibVlcPlayer: 027c16c178364a133f6ee10fc7a7d8636f92dbe8
|
||||
ExpoLibVlcPlayer: dce3d0b5847838cd5f8c5f3c3aa1bc55c92e911d
|
||||
ExpoLinearGradient: 35ebd83b16f80b3add053a2fd68cc328ed927f60
|
||||
ExpoLinking: 8d12bee174ba0cdf31239706578e29e74a417402
|
||||
ExpoLocalization: 7776ea3bdb112125390745bbaf919b734b2ad1c7
|
||||
ExpoModulesCore: c25d77625038b1968ea1afefc719862c0d8dd993
|
||||
ExpoRandom: d1444df65007bdd4070009efd5dab18e20bf0f00
|
||||
ExpoScreenOrientation: af8b31d3164239a4ef3ea0b32bd63fb65df70d58
|
||||
ExpoSharing: 032c01bb034319e2374badf082ae935be866d2e9
|
||||
ExpoSharing: 849a5ce9985c22598c16ec027e32969be8062e8e
|
||||
ExpoSystemUI: b82a45cf0f6a4fa18d07c46deba8725dd27688b4
|
||||
ExpoWebBrowser: a212e6b480d8857d3e441fba51e0c968333803b3
|
||||
EXStructuredHeaders: 09c70347b282e3d2507e25fb4c747b1b885f87f6
|
||||
|
|
|
|||
|
|
@ -653,10 +653,10 @@ const WatchProgressDisplay = memo(({
|
|||
|
||||
</View>
|
||||
|
||||
<Text style={[isTablet ? styles.tabletWatchProgressSubText : styles.watchProgressSubText, {
|
||||
<Text style={[isTablet ? styles.tabletWatchProgressSubText : styles.watchProgressSubText, {
|
||||
color: isCompleted ? 'rgba(0,255,136,0.7)' : currentTheme.colors.textMuted,
|
||||
}]}>
|
||||
{progressData.episodeInfo} • Last watched {progressData.formattedTime}
|
||||
{progressData.episodeInfo} • {progressData.formattedTime}
|
||||
</Text>
|
||||
|
||||
{/* Trakt sync status with enhanced styling */}
|
||||
|
|
|
|||
|
|
@ -181,13 +181,15 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Playback Speed Button */}
|
||||
<TouchableOpacity style={styles.bottomButton} onPress={cyclePlaybackSpeed}>
|
||||
<Ionicons name="speedometer" size={20} color="white" />
|
||||
<Text style={styles.bottomButtonText}>
|
||||
Speed {currentPlaybackSpeed}x
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
{/* Playback Speed Button - Hidden on iOS */}
|
||||
{Platform.OS !== 'ios' && (
|
||||
<TouchableOpacity style={styles.bottomButton} onPress={cyclePlaybackSpeed}>
|
||||
<Ionicons name="speedometer" size={20} color="white" />
|
||||
<Text style={styles.bottomButtonText}>
|
||||
Speed {currentPlaybackSpeed}x
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* Audio Button - Updated to use ksAudioTracks */}
|
||||
<TouchableOpacity
|
||||
|
|
|
|||
|
|
@ -531,7 +531,7 @@ const LibraryScreen = () => {
|
|||
type: 'movie',
|
||||
poster: 'placeholder',
|
||||
year: movie.year,
|
||||
lastWatched: new Date(watchedMovie.last_watched_at).toLocaleDateString(),
|
||||
lastWatched: watchedMovie.last_watched_at, // Store raw timestamp for sorting
|
||||
plays: watchedMovie.plays,
|
||||
imdbId: movie.ids.imdb,
|
||||
traktId: movie.ids.trakt,
|
||||
|
|
@ -551,7 +551,7 @@ const LibraryScreen = () => {
|
|||
type: 'series',
|
||||
poster: 'placeholder',
|
||||
year: show.year,
|
||||
lastWatched: new Date(watchedShow.last_watched_at).toLocaleDateString(),
|
||||
lastWatched: watchedShow.last_watched_at, // Store raw timestamp for sorting
|
||||
plays: watchedShow.plays,
|
||||
imdbId: show.ids.imdb,
|
||||
traktId: show.ids.trakt,
|
||||
|
|
@ -573,7 +573,7 @@ const LibraryScreen = () => {
|
|||
type: 'movie',
|
||||
poster: 'placeholder',
|
||||
year: item.movie.year,
|
||||
lastWatched: new Date(item.paused_at).toLocaleDateString(),
|
||||
lastWatched: item.paused_at, // Store raw timestamp for sorting
|
||||
imdbId: item.movie.ids.imdb,
|
||||
traktId: item.movie.ids.trakt,
|
||||
images: item.movie.images,
|
||||
|
|
@ -585,7 +585,7 @@ const LibraryScreen = () => {
|
|||
type: 'series',
|
||||
poster: 'placeholder',
|
||||
year: item.show.year,
|
||||
lastWatched: new Date(item.paused_at).toLocaleDateString(),
|
||||
lastWatched: item.paused_at, // Store raw timestamp for sorting
|
||||
imdbId: item.show.ids.imdb,
|
||||
traktId: item.show.ids.trakt,
|
||||
images: item.show.images,
|
||||
|
|
@ -607,7 +607,7 @@ const LibraryScreen = () => {
|
|||
type: 'movie',
|
||||
poster: 'placeholder',
|
||||
year: movie.year,
|
||||
lastWatched: new Date(watchlistMovie.listed_at).toLocaleDateString(),
|
||||
lastWatched: watchlistMovie.listed_at, // Store raw timestamp for sorting
|
||||
imdbId: movie.ids.imdb,
|
||||
traktId: movie.ids.trakt,
|
||||
images: movie.images,
|
||||
|
|
@ -626,7 +626,7 @@ const LibraryScreen = () => {
|
|||
type: 'series',
|
||||
poster: 'placeholder',
|
||||
year: show.year,
|
||||
lastWatched: new Date(watchlistShow.listed_at).toLocaleDateString(),
|
||||
lastWatched: watchlistShow.listed_at, // Store raw timestamp for sorting
|
||||
imdbId: show.ids.imdb,
|
||||
traktId: show.ids.trakt,
|
||||
images: show.images,
|
||||
|
|
@ -648,7 +648,7 @@ const LibraryScreen = () => {
|
|||
type: 'movie',
|
||||
poster: 'placeholder',
|
||||
year: movie.year,
|
||||
lastWatched: new Date(collectionMovie.collected_at).toLocaleDateString(),
|
||||
lastWatched: collectionMovie.collected_at, // Store raw timestamp for sorting
|
||||
imdbId: movie.ids.imdb,
|
||||
traktId: movie.ids.trakt,
|
||||
images: movie.images,
|
||||
|
|
@ -667,7 +667,7 @@ const LibraryScreen = () => {
|
|||
type: 'series',
|
||||
poster: 'placeholder',
|
||||
year: show.year,
|
||||
lastWatched: new Date(collectionShow.collected_at).toLocaleDateString(),
|
||||
lastWatched: collectionShow.collected_at, // Store raw timestamp for sorting
|
||||
imdbId: show.ids.imdb,
|
||||
traktId: show.ids.trakt,
|
||||
images: show.images,
|
||||
|
|
@ -689,7 +689,7 @@ const LibraryScreen = () => {
|
|||
type: 'movie',
|
||||
poster: 'placeholder',
|
||||
year: movie.year,
|
||||
lastWatched: new Date(ratedItem.rated_at).toLocaleDateString(),
|
||||
lastWatched: ratedItem.rated_at, // Store raw timestamp for sorting
|
||||
rating: ratedItem.rating,
|
||||
imdbId: movie.ids.imdb,
|
||||
traktId: movie.ids.trakt,
|
||||
|
|
@ -703,7 +703,7 @@ const LibraryScreen = () => {
|
|||
type: 'series',
|
||||
poster: 'placeholder',
|
||||
year: show.year,
|
||||
lastWatched: new Date(ratedItem.rated_at).toLocaleDateString(),
|
||||
lastWatched: ratedItem.rated_at, // Store raw timestamp for sorting
|
||||
rating: ratedItem.rating,
|
||||
imdbId: show.ids.imdb,
|
||||
traktId: show.ids.trakt,
|
||||
|
|
@ -715,7 +715,7 @@ const LibraryScreen = () => {
|
|||
break;
|
||||
}
|
||||
|
||||
// Sort by last watched/added date (most recent first)
|
||||
// Sort by last watched/added date (most recent first) using raw timestamps
|
||||
return items.sort((a, b) => {
|
||||
const dateA = a.lastWatched ? new Date(a.lastWatched).getTime() : 0;
|
||||
const dateB = b.lastWatched ? new Date(b.lastWatched).getTime() : 0;
|
||||
|
|
|
|||
|
|
@ -705,25 +705,32 @@ export const StreamsScreen = () => {
|
|||
|
||||
}, [loadingStreams, loadingEpisodeStreams, groupedStreams, episodeStreams, type]);
|
||||
|
||||
// Reset autoplay state when episode changes (but preserve fromPlayer logic)
|
||||
useEffect(() => {
|
||||
// Reset autoplay triggered state when episode changes
|
||||
// This allows autoplay to work for each episode individually
|
||||
setAutoplayTriggered(false);
|
||||
}, [selectedEpisode]);
|
||||
|
||||
// Reset the selected provider to 'all' if the current selection is no longer available
|
||||
// But preserve special filter values like 'grouped-plugins' and 'all'
|
||||
useEffect(() => {
|
||||
// Don't reset if it's a special filter value
|
||||
const isSpecialFilter = selectedProvider === 'all' || selectedProvider === 'grouped-plugins';
|
||||
|
||||
|
||||
if (isSpecialFilter) {
|
||||
return; // Always preserve special filters
|
||||
}
|
||||
|
||||
|
||||
// Check if provider exists in current streams data
|
||||
const currentStreamsData = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams;
|
||||
const hasStreamsForProvider = currentStreamsData[selectedProvider] &&
|
||||
currentStreamsData[selectedProvider].streams &&
|
||||
const hasStreamsForProvider = currentStreamsData[selectedProvider] &&
|
||||
currentStreamsData[selectedProvider].streams &&
|
||||
currentStreamsData[selectedProvider].streams.length > 0;
|
||||
|
||||
|
||||
// Only reset if the provider doesn't exist in available providers AND doesn't have streams
|
||||
const isAvailableProvider = availableProviders.has(selectedProvider);
|
||||
|
||||
|
||||
if (!isAvailableProvider && !hasStreamsForProvider) {
|
||||
setSelectedProvider('all');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -797,7 +797,32 @@ export class TraktService {
|
|||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
logger.error(`[TraktService] API Error ${response.status} for ${endpoint}:`, errorText);
|
||||
|
||||
// Enhanced error logging for debugging
|
||||
logger.error(`[TraktService] API Error ${response.status} for ${endpoint}:`, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
errorText: errorText,
|
||||
requestBody: body ? JSON.stringify(body, null, 2) : 'No body',
|
||||
headers: Object.fromEntries(response.headers.entries())
|
||||
});
|
||||
|
||||
// Handle 404 errors more gracefully - they might indicate content not found in Trakt
|
||||
if (response.status === 404) {
|
||||
logger.warn(`[TraktService] Content not found in Trakt database (404) for ${endpoint}. This might indicate:`);
|
||||
logger.warn(`[TraktService] 1. Invalid IMDb ID: ${body?.movie?.ids?.imdb || body?.show?.ids?.imdb || 'N/A'}`);
|
||||
logger.warn(`[TraktService] 2. Content not in Trakt database: ${body?.movie?.title || body?.show?.title || 'N/A'}`);
|
||||
logger.warn(`[TraktService] 3. Authentication issues with token`);
|
||||
|
||||
// Return a graceful response for 404s instead of throwing
|
||||
return {
|
||||
id: 0,
|
||||
action: 'not_found',
|
||||
progress: body?.progress || 0,
|
||||
error: 'Content not found in Trakt database'
|
||||
} as any;
|
||||
}
|
||||
|
||||
throw new Error(`API request failed: ${response.status}`);
|
||||
}
|
||||
|
||||
|
|
@ -1199,6 +1224,13 @@ export class TraktService {
|
|||
*/
|
||||
public async startWatching(contentData: TraktContentData, progress: number): Promise<TraktScrobbleResponse | null> {
|
||||
try {
|
||||
// Validate content data before making API call
|
||||
const validation = this.validateContentData(contentData);
|
||||
if (!validation.isValid) {
|
||||
logger.error('[TraktService] Invalid content data for start watching:', validation.errors);
|
||||
return null;
|
||||
}
|
||||
|
||||
const payload = await this.buildScrobblePayload(contentData, progress);
|
||||
if (!payload) {
|
||||
return null;
|
||||
|
|
@ -1216,6 +1248,13 @@ export class TraktService {
|
|||
*/
|
||||
public async pauseWatching(contentData: TraktContentData, progress: number): Promise<TraktScrobbleResponse | null> {
|
||||
try {
|
||||
// Validate content data before making API call
|
||||
const validation = this.validateContentData(contentData);
|
||||
if (!validation.isValid) {
|
||||
logger.error('[TraktService] Invalid content data for pause watching:', validation.errors);
|
||||
return null;
|
||||
}
|
||||
|
||||
const payload = await this.buildScrobblePayload(contentData, progress);
|
||||
if (!payload) {
|
||||
return null;
|
||||
|
|
@ -1233,6 +1272,13 @@ export class TraktService {
|
|||
*/
|
||||
public async stopWatching(contentData: TraktContentData, progress: number): Promise<TraktScrobbleResponse | null> {
|
||||
try {
|
||||
// Validate content data before making API call
|
||||
const validation = this.validateContentData(contentData);
|
||||
if (!validation.isValid) {
|
||||
logger.error('[TraktService] Invalid content data for stop watching:', validation.errors);
|
||||
return null;
|
||||
}
|
||||
|
||||
const payload = await this.buildScrobblePayload(contentData, progress);
|
||||
if (!payload) {
|
||||
return null;
|
||||
|
|
@ -1254,12 +1300,73 @@ export class TraktService {
|
|||
return this.stopWatching(contentData, progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate content data before making API calls
|
||||
*/
|
||||
private validateContentData(contentData: TraktContentData): { isValid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!contentData.type || !['movie', 'episode'].includes(contentData.type)) {
|
||||
errors.push('Invalid content type');
|
||||
}
|
||||
|
||||
if (!contentData.title || contentData.title.trim() === '') {
|
||||
errors.push('Missing or empty title');
|
||||
}
|
||||
|
||||
if (!contentData.imdbId || contentData.imdbId.trim() === '') {
|
||||
errors.push('Missing or empty IMDb ID');
|
||||
}
|
||||
|
||||
if (contentData.type === 'episode') {
|
||||
if (!contentData.season || contentData.season < 1) {
|
||||
errors.push('Invalid season number');
|
||||
}
|
||||
if (!contentData.episode || contentData.episode < 1) {
|
||||
errors.push('Invalid episode number');
|
||||
}
|
||||
if (!contentData.showTitle || contentData.showTitle.trim() === '') {
|
||||
errors.push('Missing or empty show title');
|
||||
}
|
||||
if (!contentData.showYear || contentData.showYear < 1900) {
|
||||
errors.push('Invalid show year');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build scrobble payload for API requests
|
||||
*/
|
||||
private async buildScrobblePayload(contentData: TraktContentData, progress: number): Promise<any | null> {
|
||||
try {
|
||||
// Enhanced debug logging for payload building
|
||||
logger.log('[TraktService] Building scrobble payload:', {
|
||||
type: contentData.type,
|
||||
title: contentData.title,
|
||||
imdbId: contentData.imdbId,
|
||||
year: contentData.year,
|
||||
season: contentData.season,
|
||||
episode: contentData.episode,
|
||||
showTitle: contentData.showTitle,
|
||||
showYear: contentData.showYear,
|
||||
showImdbId: contentData.showImdbId,
|
||||
progress: progress
|
||||
});
|
||||
|
||||
if (contentData.type === 'movie') {
|
||||
if (!contentData.imdbId || !contentData.title) {
|
||||
logger.error('[TraktService] Missing movie data for scrobbling:', {
|
||||
imdbId: contentData.imdbId,
|
||||
title: contentData.title
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clean IMDB ID - some APIs want it without 'tt' prefix
|
||||
const cleanImdbId = contentData.imdbId.startsWith('tt')
|
||||
? contentData.imdbId.substring(2)
|
||||
|
|
@ -1276,11 +1383,16 @@ export class TraktService {
|
|||
progress: Math.round(progress * 100) / 100 // Round to 2 decimal places
|
||||
};
|
||||
|
||||
// Movie payload logging removed
|
||||
logger.log('[TraktService] Movie payload built:', payload);
|
||||
return payload;
|
||||
} else if (contentData.type === 'episode') {
|
||||
if (!contentData.season || !contentData.episode || !contentData.showTitle || !contentData.showYear) {
|
||||
logger.error('[TraktService] Missing episode data for scrobbling');
|
||||
logger.error('[TraktService] Missing episode data for scrobbling:', {
|
||||
season: contentData.season,
|
||||
episode: contentData.episode,
|
||||
showTitle: contentData.showTitle,
|
||||
showYear: contentData.showYear
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -1318,7 +1430,7 @@ export class TraktService {
|
|||
payload.episode.ids.imdb = cleanEpisodeImdbId;
|
||||
}
|
||||
|
||||
// Episode payload logging removed
|
||||
logger.log('[TraktService] Episode payload built:', payload);
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue