1
0

Neu: ZDF Mediathek Serien Bridge

This commit is contained in:
Akamaru
2025-12-26 18:09:33 +01:00
parent 2a45795372
commit 167e642f47
2 changed files with 306 additions and 0 deletions

View File

@@ -348,6 +348,17 @@ Diese Sammlung enthält verschiedene Bridge-Implementierungen für RSS-Bridge, u
- **Sprache**: Wähle zwischen Englisch (Standard), Spanisch, Deutsch, Französisch, Italienisch und Chinesisch
- **Plattform**: Wähle zwischen Windows (Standard) oder Mac
### [ZDF Mediathek Serien Bridge](https://bridge.ponywave.de/#bridge-ZDFMediathekSeriesBridge) (Von Akamaru)
- **Beschreibung**: Gibt die neuesten Episoden einer Show aus der ZDF Mediathek zurück
- **Parameter**:
- **Show-Slug**: Der Show-Identifier aus der URL (z.B. "welke-und-pastewka-102" aus /shows/welke-und-pastewka-102)
- **Limit** (optional): Maximale Anzahl an Episoden (Standard: 20)
- **Hinweise**:
- Nutzt die offizielle ZDF Content API
- Unterstützt automatische URL-Erkennung für /shows/ und /video/shows/ URLs
- Feed-Items enthalten Titel, Thumbnail, Teaser-Beschreibung und Sendedatum
- Episoden sind nach Datum sortiert (neueste zuerst)
## Installation
1. Kopiere die gewünschten Bridge-Dateien in das `bridges`-Verzeichnis deiner RSS-Bridge-Installation

View File

@@ -0,0 +1,295 @@
<?php
declare(strict_types=1);
class ZDFMediathekSeriesBridge extends BridgeAbstract
{
const NAME = 'ZDF Mediathek Serien Bridge';
const URI = 'https://www.zdf.de';
const API_URI = 'https://api.zdf.de/content/documents/';
const API_AUTH = 'Bearer aa3noh4ohz9eeboo8shiesheec9ciequ9Quah7el';
const DESCRIPTION = 'Gibt die neuesten Episoden einer Show aus der ZDF Mediathek zurück.';
const MAINTAINER = 'Akamaru';
const CACHE_TIMEOUT = 3600; // 1 Stunde
const PARAMETERS = [
'Show' => [
'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 .= '<img src="' . htmlspecialchars($imageUrl) . '" alt="' . htmlspecialchars($title) . '" /><br>';
$item['enclosures'] = [$imageUrl];
}
// Add description
if (!empty($episode['description'])) {
$content .= '<p>' . htmlspecialchars($episode['description']) . '</p>';
}
$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;
}
}