[ 'name' => 'Series Slug', 'type' => 'text', 'required' => true, 'exampleValue' => 'hot-ones', 'title' => 'Series slug from the URL (e.g., "hot-ones" from https://www.thetvdb.com/series/hot-ones)' ], 'limit' => [ 'name' => 'Max Episodes', 'type' => 'number', 'required' => false, 'defaultValue' => 20, 'title' => 'Maximum number of episodes to return (default: 20)' ] ] ]; public function collectData() { $series = $this->getInput('series'); $limit = $this->getInput('limit') ?? 20; $url = self::URI . 'series/' . $series . '/allseasons/official'; $html = getSimpleHTMLDOM($url); if (!$html) { throw new \Exception('Unable to load page: ' . $url); } // Extract series title for author field // Try to get series title from the page title $titleElement = $html->find('title', 0); if ($titleElement) { // Format: "Hot Ones - Aired Order - All Seasons - TheTVDB.com" $pageTitle = trim($titleElement->plaintext); // Take only the first part before " - " $parts = explode(' - ', $pageTitle); if (!empty($parts[0])) { $this->seriesTitle = trim($parts[0]); } } // Find all episode list items $episodes = $html->find('li.list-group-item'); if (empty($episodes)) { throw new \Exception('No episodes found. Please check if the series slug is correct.'); } // Reverse to get newest episodes first $episodes = array_reverse($episodes); $count = 0; foreach ($episodes as $episode) { if ($count >= $limit) { break; } // Skip if this is not an episode (some list items might be headers) $heading = $episode->find('h4.list-group-item-heading', 0); if (!$heading) { continue; } // Check if this is a special episode $isSpecial = strpos($episode->class, 'list-group-item-special') !== false; // Extract episode label (S28E08 or SPECIAL 0x202) $labelElement = $isSpecial ? $episode->find('small.episode-label', 0) : $episode->find('span.episode-label', 0); $episodeLabel = $labelElement ? trim($labelElement->plaintext) : ''; // Convert SPECIAL format to S00E## format if ($isSpecial && preg_match('/SPECIAL\s+0x(\d+)/i', $episodeLabel, $matches)) { $episodeLabel = 'S00E' . str_pad($matches[1], 2, '0', STR_PAD_LEFT); } // Extract title and URL $titleLink = $episode->find('h4 a', 0); if (!$titleLink) { continue; // Skip if no title link found } $title = trim($titleLink->plaintext); $episodeUrl = self::URI . ltrim($titleLink->href, '/'); // Extract air date $dateElement = $episode->find('ul.list-inline li', 0); $airDate = $dateElement ? trim($dateElement->plaintext) : ''; // Extract network/platform $networkElement = $episode->find('ul.list-inline li', 1); $network = $networkElement ? trim($networkElement->plaintext) : ''; // Extract description $descElement = $episode->find('.list-group-item-text p', 0); $description = $descElement ? trim($descElement->innertext) : ''; // Extract thumbnail image $imgElement = $episode->find('.list-group-item-text img', 0); $thumbnail = ''; if ($imgElement) { // Check for lazy-loaded images (data-src) first, then fallback to src $thumbnail = $imgElement->getAttribute('data-src') ?: $imgElement->src; // Make sure URL is absolute if ($thumbnail && !str_starts_with($thumbnail, 'http')) { $thumbnail = 'https:' . $thumbnail; } } // Build content HTML $content = ''; // Add thumbnail if available if ($thumbnail) { $content .= '
' . $description . '
'; } // Parse timestamp from air date $timestamp = null; if ($airDate) { $parsedTime = strtotime($airDate); if ($parsedTime !== false) { $timestamp = $parsedTime; } } // Create RSS item $item = [ 'uri' => $episodeUrl, 'title' => $this->seriesTitle ? ($this->seriesTitle . ' ' . $episodeLabel . ' - ' . $title) : ($episodeLabel . ' - ' . $title), 'author' => $this->seriesTitle, 'content' => $content, 'timestamp' => $timestamp, 'uid' => $episodeUrl, ]; $this->items[] = $item; $count++; } } public function getName() { if ($this->seriesTitle) { return $this->seriesTitle . ' - TheTVDB'; } return parent::getName(); } public function getIcon() { return 'https://www.google.com/s2/favicons?domain=thetvdb.com&sz=32'; } }