diff --git a/TVMazeSeriesBridge.php b/TVMazeSeriesBridge.php
new file mode 100644
index 0000000..1a65f78
--- /dev/null
+++ b/TVMazeSeriesBridge.php
@@ -0,0 +1,193 @@
+ [
+ 'show_id' => [
+ 'name' => 'TVMaze Show ID',
+ 'type' => 'number',
+ 'required' => true,
+ 'exampleValue' => '54421',
+ 'title' => 'Find the Show ID in the URL: tvmaze.com/shows/ID/name'
+ ],
+ 'limit' => [
+ 'name' => 'Maximum number of episodes',
+ 'type' => 'number',
+ 'required' => false,
+ 'defaultValue' => 50
+ ]
+ ]
+ ];
+
+ private $showTitle = '';
+ private $showUrl = '';
+ private $officialSite = '';
+ private $webChannelName = '';
+
+ public function getIcon()
+ {
+ // Use streaming service favicon if available
+ if (!empty($this->officialSite)) {
+ $host = parse_url($this->officialSite, PHP_URL_HOST);
+ if ($host) {
+ return 'https://www.google.com/s2/favicons?domain=' . $host . '&sz=32';
+ }
+ }
+ return 'https://www.google.com/s2/favicons?domain=www.tvmaze.com&sz=32';
+ }
+
+ public function getName()
+ {
+ if (!empty($this->showTitle)) {
+ $suffix = !empty($this->webChannelName) ? $this->webChannelName : 'TVMaze';
+ return $this->showTitle . ' - ' . $suffix;
+ }
+ return parent::getName();
+ }
+
+ public function getURI()
+ {
+ // Prefer officialSite (e.g. Amazon Prime link)
+ if (!empty($this->officialSite)) {
+ return $this->officialSite;
+ }
+
+ if (!empty($this->showUrl)) {
+ return $this->showUrl;
+ }
+
+ $showId = $this->getInput('show_id');
+ if (!empty($showId)) {
+ return self::URI . '/shows/' . $showId;
+ }
+
+ return self::URI;
+ }
+
+ public function collectData()
+ {
+ $showId = $this->getInput('show_id');
+ $limit = $this->getInput('limit') ?? 50;
+
+ if (empty($showId)) {
+ returnClientError('Show ID is required.');
+ }
+
+ // First fetch show information
+ $showUrl = 'https://api.tvmaze.com/shows/' . urlencode((string)$showId);
+ $showJson = getContents($showUrl);
+ $showData = json_decode($showJson, true);
+
+ if (!$showData) {
+ returnServerError('Show not found. Please check the Show ID.');
+ }
+
+ $this->showTitle = $showData['name'] ?? 'Unknown Series';
+ $this->showUrl = $showData['url'] ?? '';
+ $this->officialSite = $showData['officialSite'] ?? '';
+ $this->webChannelName = $showData['webChannel']['name'] ?? $showData['network']['name'] ?? '';
+
+ // Fetch episodes
+ $episodesUrl = 'https://api.tvmaze.com/shows/' . urlencode((string)$showId) . '/episodes';
+ $episodesJson = getContents($episodesUrl);
+ $episodes = json_decode($episodesJson, true);
+
+ if (!$episodes || !is_array($episodes)) {
+ returnServerError('No episodes found.');
+ }
+
+ // Sort by season and episode (newest first for feed)
+ usort($episodes, function ($a, $b) {
+ // First by season, then by episode (descending)
+ if ($a['season'] !== $b['season']) {
+ return $b['season'] - $a['season'];
+ }
+ return $b['number'] - $a['number'];
+ });
+
+ // Limit the number of episodes
+ $episodes = array_slice($episodes, 0, (int)$limit);
+
+ foreach ($episodes as $episode) {
+ $item = [];
+
+ // Title: "Series Title S01E01: Episode Title"
+ $seasonNum = $episode['season'] ?? 1;
+ $episodeNum = $episode['number'] ?? 0;
+ $episodeTitle = $episode['name'] ?? 'Unknown Episode';
+
+ $item['title'] = sprintf(
+ '%s S%02dE%02d: %s',
+ $this->showTitle,
+ $seasonNum,
+ $episodeNum,
+ $episodeTitle
+ );
+
+ // Episode URL (prefer streaming service officialSite)
+ if (!empty($this->officialSite)) {
+ $item['uri'] = $this->officialSite;
+ } else {
+ $item['uri'] = $episode['url'] ?? self::URI;
+ }
+
+ // Author: Streaming service name
+ if (!empty($this->webChannelName)) {
+ $item['author'] = $this->webChannelName;
+ }
+
+ // Unique ID
+ $item['uid'] = 'tvmaze-' . ($episode['id'] ?? $showId . '-' . $seasonNum . '-' . $episodeNum);
+
+ // Timestamp from airdate
+ if (!empty($episode['airdate'])) {
+ $timestamp = strtotime($episode['airdate']);
+ if ($timestamp !== false) {
+ $item['timestamp'] = $timestamp;
+ }
+ }
+
+ // Content: Image + Description + Details
+ $content = '';
+
+ // Thumbnail
+ $imageUrl = $episode['image']['original'] ?? $episode['image']['medium'] ?? null;
+ if ($imageUrl) {
+ $content .= '
';
+ $item['enclosures'] = [$imageUrl];
+ }
+
+ // Description
+ $summary = $episode['summary'] ?? '';
+ if (!empty($summary)) {
+ // TVMaze returns HTML, keep it for content
+ $content .= '
' . $summary . '
'; + } + + $item['content'] = $content; + + $this->items[] = $item; + } + } + + public function detectParameters($url) + { + // URL-Format: https://www.tvmaze.com/shows/54421/lol-last-one-laughing + if (preg_match('#tvmaze\.com/shows/(\d+)#i', $url, $matches)) { + return [ + 'show_id' => $matches[1] + ]; + } + + return null; + } +}