Browse Source

feat: JellyStat metadata now uses Emby/Jellyfin as fallback source

- JellyStat metadata function now checks if Emby or Jellyfin is configured
- When available, fetches rich metadata directly from the media server
- Jellyfin is preferred over Emby when both are configured (since JellyStat tracks Jellyfin)
- Falls back to JellyStat's own endpoints if media servers aren't configured
- This provides rich metadata for JellyStat homepage items when a compatible media server is available
mgomon 7 months ago
parent
commit
d90c8eb025
3 changed files with 301 additions and 36 deletions
  1. 125 36
      api/homepage/jellystat.php
  2. 139 0
      debug_jellystat_metadata.php
  3. 37 0
      test_jellystat_api.html

+ 125 - 36
api/homepage/jellystat.php

@@ -221,48 +221,137 @@ trait JellyStatHomepageItem
                 return false;
             }
 
-            // Prepare URLs and options
-            $url = $this->config['jellyStatURL'] ?? '';
-            $internalUrl = $this->config['jellyStatInternalURL'] ?? '';
-            $apiUrl = !empty($internalUrl) ? $internalUrl : $url;
-            $token = $this->config['jellyStatApikey'] ?? '';
-            $disableCert = $this->config['jellyStatDisableCertCheck'] ?? false;
-            $customCert = $this->config['jellyStatUseCustomCertificate'] ?? false;
-            $options = $this->requestOptions($apiUrl, null, $disableCert, $customCert);
-
-            $details = null;
-            $this->info("JellyStat metadata: Attempting to fetch metadata for key: {$key}");
+            // First, try to use Emby/Jellyfin if configured
+            // JellyStat tracks Jellyfin/Emby servers, so we can use their metadata
+            $useEmbyMetadata = false;
+            $useJellyfinMetadata = false;
             
-            // Try multiple JellyStat/proxy endpoints to get detailed item information
-            $tryEndpoints = [];
-            if ($token !== '') {
-                // Try JellyStat's native item detail endpoints first
-                $tryEndpoints[] = rtrim($this->qualifyURL($apiUrl), '/') . '/api/getItem?apiKey=' . urlencode($token) . '&id=' . urlencode($key);
-                $tryEndpoints[] = rtrim($this->qualifyURL($apiUrl), '/') . '/api/getItemById?apiKey=' . urlencode($token) . '&id=' . urlencode($key);
+            // Check if Emby is configured and enabled
+            if ($this->config['homepageEmbyEnabled'] && !empty($this->config['embyURL']) && !empty($this->config['embyToken'])) {
+                $this->info('JellyStat metadata: Emby is configured, will try to use it for metadata');
+                $useEmbyMetadata = true;
             }
-            // Try proxying directly to Jellyfin/Emby items endpoint
-            $tryEndpoints[] = rtrim($this->qualifyURL($apiUrl), '/') . '/proxy/Items/' . rawurlencode($key);
-            // Also try with Fields parameter to get comprehensive metadata
-            $tryEndpoints[] = rtrim($this->qualifyURL($apiUrl), '/') . '/proxy/Items/' . rawurlencode($key) . '?Fields=Overview,People,Genres,CommunityRating,CriticRating,Studios,Taglines,ProductionYear,PremiereDate';
-
-            foreach ($tryEndpoints as $index => $endpoint) {
+            
+            // Check if Jellyfin is configured and enabled (Jellyfin uses same backend as Emby)
+            if ($this->config['homepageJellyfinEnabled'] && !empty($this->config['jellyfinURL']) && !empty($this->config['jellyfinToken'])) {
+                $this->info('JellyStat metadata: Jellyfin is configured, will try to use it for metadata');
+                $useJellyfinMetadata = true;
+            }
+            
+            // Try to get metadata from Emby/Jellyfin first
+            if ($useEmbyMetadata || $useJellyfinMetadata) {
+                $this->info('JellyStat metadata: Attempting to fetch metadata from configured media server');
+                
+                // Use Jellyfin preferentially if both are configured (since JellyStat is for Jellyfin)
+                if ($useJellyfinMetadata) {
+                    // Jellyfin uses the same Emby trait, just with different config keys
+                    $mediaServerUrl = $this->qualifyURL($this->config['jellyfinURL']);
+                    $mediaServerToken = $this->config['jellyfinToken'];
+                    $disableCert = $this->config['jellyfinDisableCertCheck'] ?? false;
+                    $customCert = $this->config['jellyfinUseCustomCertificate'] ?? false;
+                    $serverType = 'jellyfin';
+                } else {
+                    $mediaServerUrl = $this->qualifyURL($this->config['embyURL']);
+                    $mediaServerToken = $this->config['embyToken'];
+                    $disableCert = $this->config['embyDisableCertCheck'] ?? false;
+                    $customCert = $this->config['embyUseCustomCertificate'] ?? false;
+                    $serverType = 'emby';
+                }
+                
+                // Try to fetch metadata directly from Emby/Jellyfin
                 try {
-                    $this->info("JellyStat metadata: Trying endpoint {$index}: {$endpoint}");
-                    $resp = Requests::get($endpoint, [], $options);
-                    if ($resp->success) {
-                        $json = json_decode($resp->body, true);
-                        if (is_array($json) && !isset($json['error']) && !empty($json)) {
-                            $this->info("JellyStat metadata: Successfully fetched data from endpoint {$index}");
-                            $details = $json;
-                            break;
-                        } else {
-                            $this->info("JellyStat metadata: Endpoint {$index} returned invalid or empty data");
+                    $this->info("JellyStat metadata: Trying to fetch from {$serverType} server");
+                    
+                    // Get the item metadata directly from Emby/Jellyfin API
+                    $options = $this->requestOptions($mediaServerUrl, 60, $disableCert, $customCert);
+                    
+                    // First, get a user ID (preferably admin)
+                    $userIds = $mediaServerUrl . "/Users?api_key=" . $mediaServerToken;
+                    $response = Requests::get($userIds, [], $options);
+                    
+                    if ($response->success) {
+                        $users = json_decode($response->body, true);
+                        $userId = null;
+                        
+                        // Find an admin user
+                        foreach ($users as $user) {
+                            if (isset($user['Policy']) && isset($user['Policy']['IsAdministrator']) && $user['Policy']['IsAdministrator']) {
+                                $userId = $user['Id'];
+                                break;
+                            }
+                        }
+                        
+                        // If no admin found, use first user
+                        if (!$userId && !empty($users)) {
+                            $userId = $users[0]['Id'];
+                        }
+                        
+                        if ($userId) {
+                            // Fetch the item metadata
+                            $metadataUrl = $mediaServerUrl . '/Users/' . $userId . '/Items/' . $key . '?EnableImages=true&api_key=' . $mediaServerToken . '&Fields=Overview,People,Genres,CommunityRating,CriticRating,Studios,Taglines,ProductionYear,PremiereDate,RunTimeTicks';
+                            $metadataResponse = Requests::get($metadataUrl, [], $options);
+                            
+                            if ($metadataResponse->success) {
+                                $details = json_decode($metadataResponse->body, true);
+                                if (is_array($details) && !empty($details)) {
+                                    $this->info('JellyStat metadata: Successfully fetched metadata from ' . $serverType);
+                                    // Process this using existing Emby metadata processing
+                                    // The structure will be handled below in the existing processing code
+                                }
+                            } else {
+                                $this->info('JellyStat metadata: Failed to fetch item from ' . $serverType . ' - Status: ' . $metadataResponse->status_code);
+                            }
                         }
-                    } else {
-                        $this->info("JellyStat metadata: Endpoint {$index} failed with status {$resp->status_code}");
                     }
                 } catch (\Throwable $e) {
-                    $this->info("JellyStat metadata: Endpoint {$index} threw exception: " . $e->getMessage());
+                    $this->info('JellyStat metadata: Exception while fetching from media server: ' . $e->getMessage());
+                }
+            }
+            
+            // If we don't have details from Emby/Jellyfin, try JellyStat's own endpoints (legacy fallback)
+            if (!$details) {
+                $this->info('JellyStat metadata: No metadata from media servers, trying JellyStat endpoints');
+                
+                // Prepare URLs and options for JellyStat
+                $url = $this->config['jellyStatURL'] ?? '';
+                $internalUrl = $this->config['jellyStatInternalURL'] ?? '';
+                $apiUrl = !empty($internalUrl) ? $internalUrl : $url;
+                $token = $this->config['jellyStatApikey'] ?? '';
+                $disableCert = $this->config['jellyStatDisableCertCheck'] ?? false;
+                $customCert = $this->config['jellyStatUseCustomCertificate'] ?? false;
+                $options = $this->requestOptions($apiUrl, null, $disableCert, $customCert);
+                
+                // Try multiple JellyStat/proxy endpoints to get detailed item information
+                $tryEndpoints = [];
+                if ($token !== '') {
+                    // Try JellyStat's native item detail endpoints first
+                    $tryEndpoints[] = rtrim($this->qualifyURL($apiUrl), '/') . '/api/getItem?apiKey=' . urlencode($token) . '&id=' . urlencode($key);
+                    $tryEndpoints[] = rtrim($this->qualifyURL($apiUrl), '/') . '/api/getItemById?apiKey=' . urlencode($token) . '&id=' . urlencode($key);
+                }
+                // Try proxying directly to Jellyfin/Emby items endpoint
+                $tryEndpoints[] = rtrim($this->qualifyURL($apiUrl), '/') . '/proxy/Items/' . rawurlencode($key);
+                // Also try with Fields parameter to get comprehensive metadata
+                $tryEndpoints[] = rtrim($this->qualifyURL($apiUrl), '/') . '/proxy/Items/' . rawurlencode($key) . '?Fields=Overview,People,Genres,CommunityRating,CriticRating,Studios,Taglines,ProductionYear,PremiereDate';
+                
+                foreach ($tryEndpoints as $index => $endpoint) {
+                    try {
+                        $this->info("JellyStat metadata: Trying endpoint {$index}: {$endpoint}");
+                        $resp = Requests::get($endpoint, [], $options);
+                        if ($resp->success) {
+                            $json = json_decode($resp->body, true);
+                            if (is_array($json) && !isset($json['error']) && !empty($json)) {
+                                $this->info("JellyStat metadata: Successfully fetched data from endpoint {$index}");
+                                $details = $json;
+                                break;
+                            } else {
+                                $this->info("JellyStat metadata: Endpoint {$index} returned invalid or empty data");
+                            }
+                        } else {
+                            $this->info("JellyStat metadata: Endpoint {$index} failed with status {$resp->status_code}");
+                        }
+                    } catch (\Throwable $e) {
+                        $this->info("JellyStat metadata: Endpoint {$index} threw exception: " . $e->getMessage());
+                    }
                 }
             }
 

+ 139 - 0
debug_jellystat_metadata.php

@@ -0,0 +1,139 @@
+<?php
+/**
+ * Debug script to test JellyStat metadata functionality
+ * Run this to troubleshoot why metadata returns "Unknown Item"
+ */
+
+require_once 'api/config/config.php';
+require_once 'api/classes/organizr.php';
+
+// Create Organizr instance (this will load config)
+$organizr = new Organizr();
+
+echo "=== JellyStat Metadata Debug Tool ===\n\n";
+
+// Check configuration
+$jellyStatURL = $organizr->config['jellyStatURL'] ?? '';
+$jellyStatInternalURL = $organizr->config['jellyStatInternalURL'] ?? '';
+$jellyStatApikey = $organizr->config['jellyStatApikey'] ?? '';
+$enabled = $organizr->config['homepageJellyStatEnabled'] ?? false;
+
+echo "Configuration:\n";
+echo "- JellyStat URL: " . ($jellyStatURL ?: 'NOT SET') . "\n";
+echo "- Internal URL: " . ($jellyStatInternalURL ?: 'NOT SET') . "\n";  
+echo "- API Key: " . ($jellyStatApikey ? 'SET (****)' : 'NOT SET') . "\n";
+echo "- Plugin Enabled: " . ($enabled ? 'YES' : 'NO') . "\n\n";
+
+if (!$enabled) {
+    echo "❌ JellyStat plugin is not enabled!\n";
+    echo "Enable it in Organizr settings first.\n";
+    exit(1);
+}
+
+if (!$jellyStatURL) {
+    echo "❌ JellyStat URL is not configured!\n";
+    echo "Set jellyStatURL in Organizr settings first.\n";
+    exit(1);
+}
+
+// Test basic connectivity
+$testUrl = !empty($jellyStatInternalURL) ? $jellyStatInternalURL : $jellyStatURL;
+$testUrl = rtrim($organizr->qualifyURL($testUrl), '/');
+
+echo "Testing connectivity to: {$testUrl}\n\n";
+
+// Test endpoints that the metadata function would try
+$testKey = 'test-item-id';
+$endpoints = [];
+
+if ($jellyStatApikey) {
+    $endpoints['JellyStat API (getItem)'] = $testUrl . '/api/getItem?apiKey=' . urlencode($jellyStatApikey) . '&id=' . urlencode($testKey);
+    $endpoints['JellyStat API (getItemById)'] = $testUrl . '/api/getItemById?apiKey=' . urlencode($jellyStatApikey) . '&id=' . urlencode($testKey);
+}
+
+$endpoints['Jellyfin Proxy (basic)'] = $testUrl . '/proxy/Items/' . rawurlencode($testKey);
+$endpoints['Jellyfin Proxy (with fields)'] = $testUrl . '/proxy/Items/' . rawurlencode($testKey) . '?Fields=Overview,People,Genres,CommunityRating,CriticRating,Studios,Taglines,ProductionYear,PremiereDate';
+
+// Also test a real item ID if provided via command line
+if (isset($argv[1])) {
+    $realKey = $argv[1];
+    echo "Testing with real item ID: {$realKey}\n\n";
+    
+    if ($jellyStatApikey) {
+        $endpoints['Real Item - JellyStat API'] = $testUrl . '/api/getItem?apiKey=' . urlencode($jellyStatApikey) . '&id=' . urlencode($realKey);
+    }
+    $endpoints['Real Item - Jellyfin Proxy'] = $testUrl . '/proxy/Items/' . rawurlencode($realKey) . '?Fields=Overview,People,Genres,CommunityRating,CriticRating,Studios,Taglines,ProductionYear,PremiereDate';
+}
+
+foreach ($endpoints as $name => $url) {
+    echo "Testing {$name}:\n";
+    echo "URL: {$url}\n";
+    
+    try {
+        $options = $organizr->requestOptions($url, null, 
+            $organizr->config['jellyStatDisableCertCheck'] ?? false,
+            $organizr->config['jellyStatUseCustomCertificate'] ?? false
+        );
+        
+        $response = Requests::get($url, [], $options);
+        
+        if ($response->success) {
+            $data = json_decode($response->body, true);
+            
+            if (json_last_error() === JSON_ERROR_NONE) {
+                echo "✅ SUCCESS - Status: {$response->status_code}\n";
+                
+                if (is_array($data)) {
+                    $keys = array_keys($data);
+                    echo "Response has keys: " . implode(', ', array_slice($keys, 0, 10)) . 
+                         (count($keys) > 10 ? '... (' . count($keys) . ' total)' : '') . "\n";
+                    
+                    // Look for typical media metadata fields
+                    $mediaFields = ['Name', 'Id', 'Type', 'Overview', 'Genres', 'People', 'RunTimeTicks', 'ProductionYear'];
+                    $foundFields = array_intersect($keys, $mediaFields);
+                    if (!empty($foundFields)) {
+                        echo "Media fields found: " . implode(', ', $foundFields) . "\n";
+                        
+                        // Show sample values
+                        foreach (['Name', 'Type', 'Overview'] as $field) {
+                            if (isset($data[$field])) {
+                                $value = $data[$field];
+                                if (is_string($value) && strlen($value) > 50) {
+                                    $value = substr($value, 0, 47) . '...';
+                                }
+                                echo "{$field}: {$value}\n";
+                            }
+                        }
+                    }
+                } else {
+                    echo "Response is not an array: " . gettype($data) . "\n";
+                }
+            } else {
+                echo "❌ Invalid JSON response\n";
+                echo "Raw response: " . substr($response->body, 0, 200) . "...\n";
+            }
+        } else {
+            echo "❌ HTTP Error: {$response->status_code}\n";
+            if (!empty($response->body)) {
+                echo "Error body: " . substr($response->body, 0, 200) . "...\n";
+            }
+        }
+        
+    } catch (Exception $e) {
+        echo "❌ Exception: " . $e->getMessage() . "\n";
+    }
+    
+    echo "\n" . str_repeat('-', 60) . "\n\n";
+}
+
+echo "=== Debug Complete ===\n\n";
+
+echo "Next steps if endpoints are failing:\n";
+echo "1. Verify JellyStat is running and accessible\n";  
+echo "2. Check if API key is correct and has permissions\n";
+echo "3. Test URLs directly in browser/curl\n";
+echo "4. Check JellyStat logs for errors\n";
+echo "5. Verify firewall/network connectivity\n\n";
+
+echo "If you have a working item ID from JellyStat, run:\n";
+echo "php debug_jellystat_metadata.php YOUR-ITEM-ID\n";

+ 37 - 0
test_jellystat_api.html

@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>JellyStat API Test</title>
+</head>
+<body>
+    <h1>JellyStat Metadata API Test</h1>
+    <button onclick="testMetadata()">Test Metadata API</button>
+    <pre id="result"></pre>
+    
+    <script>
+    async function testMetadata() {
+        const token = '4wr9yn1z30k57hnsczpu';
+        const testKey = '123456'; // Replace with actual item ID
+        
+        try {
+            const response = await fetch('https://media.glassnetworks.net/api/v2/homepage/jellystat/metadata', {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                    'Token': token
+                },
+                body: JSON.stringify({ key: testKey })
+            });
+            
+            const data = await response.json();
+            document.getElementById('result').textContent = JSON.stringify(data, null, 2);
+        } catch (error) {
+            document.getElementById('result').textContent = 'Error: ' + error.message;
+        }
+    }
+    
+    // Auto-run on load
+    window.onload = testMetadata;
+    </script>
+</body>
+</html>