[ 'show_slug' => [ 'name' => 'Show-Slug (z.B. "welke-und-pastewka-102")', 'type' => 'text', 'required' => true, 'exampleValue' => 'welke-und-pastewka-102', 'title' => 'Den Show-Slug findest du in der URL: zdf.de/shows/SHOW-SLUG' ], 'limit' => [ 'name' => 'Maximale Anzahl an Episoden', 'type' => 'number', 'required' => false, 'defaultValue' => 20 ] ] ]; private $showTitle = ''; private $showDescription = ''; public function getIcon() { return 'https://www.google.com/s2/favicons?domain=www.zdf.de&sz=32'; } public function getName() { if (!empty($this->showTitle)) { return $this->showTitle . ' - ZDF Mediathek'; } return parent::getName(); } public function getURI() { $showSlug = $this->getInput('show_slug'); if (!empty($showSlug)) { return self::URI . '/shows/' . $showSlug; } return self::URI; } public function collectData() { $showSlug = $this->getInput('show_slug'); $limit = $this->getInput('limit') ?? 20; if (empty($showSlug)) { returnClientError('Show-Slug ist erforderlich.'); } // Validate show slug format if (!preg_match('/^[a-z0-9-]+-\d+$/i', $showSlug)) { returnClientError('Ungültiger Show-Slug. Format: name-123'); } // Fetch show data from ZDF API $apiUrl = self::API_URI . $showSlug . '.json?profile=default'; // Set custom headers for ZDF API authentication $curlOpts = [ CURLOPT_HTTPHEADER => [ 'Api-Auth: ' . self::API_AUTH ] ]; $jsonData = getContents($apiUrl, [], $curlOpts); if (!$jsonData) { returnServerError('Konnte die Show-Daten nicht von der ZDF API laden.'); } $data = json_decode($jsonData, true); if (!$data) { returnServerError('Konnte die JSON-Antwort nicht parsen.'); } // Extract show title $this->showTitle = $data['title'] ?? 'Unbekannte Show'; // Extract episodes from modules $episodes = []; $modules = $data['module'] ?? []; foreach ($modules as $module) { $moduleTitle = $module['title'] ?? ''; // Skip modules that don't look like episode containers // Episode modules usually have titles like "Folge X mit..." if (empty($moduleTitle) || stripos($moduleTitle, 'TV-Sendetermine') !== false || stripos($moduleTitle, 'Mehr') !== false) { continue; } $teasers = $module['teaser'] ?? []; foreach ($teasers as $teaser) { $target = $teaser['http://zdf.de/rels/target'] ?? null; if (!$target || empty($target['id'])) { continue; } // Only include episodes (not other content types) if (($target['contentType'] ?? '') !== 'episode') { continue; } $episodes[] = $this->parseEpisode($target); } } if (empty($episodes)) { returnServerError('Keine Episoden gefunden. Die Show existiert möglicherweise nicht oder hat keine Episoden.'); } // Sort by editorial date (newest first) usort($episodes, function ($a, $b) { $timeA = $a['timestamp'] ?? 0; $timeB = $b['timestamp'] ?? 0; return $timeB - $timeA; }); // Limit episodes $episodes = array_slice($episodes, 0, $limit); // Create RSS items foreach ($episodes as $episode) { $item = $this->createItemFromEpisode($episode); $this->items[] = $item; } } /** * Parse episode data from API response */ private function parseEpisode(array $target): array { $episode = []; // Title $episode['title'] = $target['teaserHeadline'] ?? $target['title'] ?? 'Unbekannte Episode'; // URL $episode['url'] = $target['webCanonical'] ?? $target['http://zdf.de/rels/sharing-url'] ?? ''; // Description $episode['description'] = $target['teasertext'] ?? $target['description'] ?? ''; // Image $imageLayouts = $target['teaserImageRef']['layouts'] ?? []; if (!empty($imageLayouts)) { // Prefer 1280x720 or 1920x1080 resolution $episode['image'] = $imageLayouts['1280x720'] ?? $imageLayouts['1920x1080'] ?? $imageLayouts['768x432'] ?? reset($imageLayouts); } else { $episode['image'] = null; } // Timestamp from editorialDate $editorialDate = $target['editorialDate'] ?? null; if ($editorialDate) { $timestamp = strtotime($editorialDate); $episode['timestamp'] = $timestamp !== false ? $timestamp : null; } else { $episode['timestamp'] = null; } // Try to extract season/episode from title or metadata $episode['season'] = null; $episode['episode'] = null; // Check if there's season/episode info in the title $title = $episode['title']; if (preg_match('/S(\d+)\s*E(\d+)/i', $title, $matches)) { $episode['season'] = (int)$matches[1]; $episode['episode'] = (int)$matches[2]; } elseif (preg_match('/Staffel\s*(\d+).*?Folge\s*(\d+)/i', $title, $matches)) { $episode['season'] = (int)$matches[1]; $episode['episode'] = (int)$matches[2]; } elseif (preg_match('/Folge\s*(\d+)/i', $title, $matches)) { // Just episode number without season $episode['episode'] = (int)$matches[1]; } // UID $episode['uid'] = $target['id'] ?? md5($episode['url']); return $episode; } /** * Create RSS item from episode data */ private function createItemFromEpisode(array $episode): array { $item = []; // Build title with season/episode info if available $title = $episode['title'] ?? 'Unbekannte Episode'; if (!empty($episode['season']) && !empty($episode['episode'])) { $seasonEp = sprintf('S%02dE%02d', (int)$episode['season'], (int)$episode['episode']); $item['title'] = $this->showTitle . ' ' . $seasonEp . ' - ' . $title; } elseif (!empty($episode['episode'])) { $item['title'] = $this->showTitle . ' E' . sprintf('%02d', (int)$episode['episode']) . ' - ' . $title; } else { $item['title'] = $this->showTitle . ' - ' . $title; } // URL $episodeUrl = $episode['url'] ?? ''; if (!empty($episodeUrl)) { // Handle both absolute and relative URLs if (strpos($episodeUrl, 'http') === 0) { $item['uri'] = $episodeUrl; } else { $item['uri'] = self::URI . (strpos($episodeUrl, '/') === 0 ? '' : '/') . $episodeUrl; } } else { $item['uri'] = $this->getURI(); } // Timestamp if (!empty($episode['timestamp'])) { $item['timestamp'] = $episode['timestamp']; } // UID $item['uid'] = $episode['uid']; // Author $item['author'] = 'ZDF'; // Content: Image + Description $content = ''; // Add image if (!empty($episode['image'])) { $imageUrl = $episode['image']; $content .= '' . htmlspecialchars($title) . '
'; $item['enclosures'] = [$imageUrl]; } // Add description if (!empty($episode['description'])) { $content .= '

' . htmlspecialchars($episode['description']) . '

'; } $item['content'] = $content; // Categories if (!empty($episode['season'])) { $item['categories'] = ['Staffel ' . $episode['season']]; } return $item; } public function detectParameters($url) { // Pattern 1: https://www.zdf.de/shows/welke-und-pastewka-102 if (preg_match('#zdf\.de/shows/([a-z0-9-]+-\d+)#i', $url, $matches)) { return [ 'show_slug' => $matches[1] ]; } // Pattern 2: Episode URLs - extract show slug from episode URL // https://www.zdf.de/video/shows/welke-und-pastewka-102/episode-slug if (preg_match('#zdf\.de/video/shows/([a-z0-9-]+-\d+)/#i', $url, $matches)) { return [ 'show_slug' => $matches[1] ]; } return null; } }