Neu: TheTVDB Bridge
This commit is contained in:
183
TheTVDBBridge.php
Normal file
183
TheTVDBBridge.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* TheTVDB Bridge
|
||||
* Returns new episodes from TheTVDB series pages
|
||||
*/
|
||||
class TheTVDBBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'TheTVDB Bridge';
|
||||
const URI = 'https://www.thetvdb.com/';
|
||||
const DESCRIPTION = 'Returns new episodes from TheTVDB series pages (English)';
|
||||
const MAINTAINER = 'Your Name';
|
||||
|
||||
private $seriesTitle = '';
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'series' => [
|
||||
'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 .= '<p><img src="' . htmlspecialchars($thumbnail) . '" style="max-width: 100%; height: auto;"></p>';
|
||||
}
|
||||
|
||||
// Add description
|
||||
if ($description) {
|
||||
$content .= '<p>' . $description . '</p>';
|
||||
}
|
||||
|
||||
// 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';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user