Neu: SerienFans Bridge
This commit is contained in:
@@ -290,6 +290,15 @@ Diese Sammlung enthält verschiedene Bridge-Implementierungen für RSS-Bridge, u
|
||||
- Titel im Format "Serienname S01E01 - Episodentitel"
|
||||
- Keine Datumsinformationen verfügbar (API liefert keine Veröffentlichungsdaten), aber jede Episode hat eine eindeutige ID
|
||||
|
||||
### [SerienFans Bridge](https://bridge.ponywave.de/#bridge-SerienFansBridge) (Von Akamaru)
|
||||
- **Beschreibung**: Gibt die neuesten Serien-Episoden von SerienFans zurück
|
||||
- **Parameter**:
|
||||
- **Limit** (optional): Maximale Anzahl an Episoden (Standard: 20, max: 50)
|
||||
- **Hinweise**:
|
||||
- Gruppiert mehrere Releases der gleichen Episode (z.B. verschiedene Qualitäten) in einem Feed-Item
|
||||
- Zeigt Serien-Namen, Cover-Bilder und Episode-Informationen (Season/Episode)
|
||||
- Sortiert nach Upload-Zeit (neueste zuerst)
|
||||
|
||||
### [Snowbreak News Bridge](https://bridge.ponywave.de/#bridge-SnowbreakNewsBridge) (Von Akamaru)
|
||||
- **Beschreibung**: Zeigt die neuesten Nachrichten von Snowbreak: Containment Zone
|
||||
|
||||
|
||||
231
SerienFansBridge.php
Normal file
231
SerienFansBridge.php
Normal file
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class SerienFansBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'SerienFans';
|
||||
const URI = 'https://serienfans.org/';
|
||||
const DESCRIPTION = 'Gibt die neuesten Episoden von SerienFans zurück';
|
||||
const MAINTAINER = 'Akamaru';
|
||||
const CACHE_TIMEOUT = 1800; // 30 Minuten
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'limit' => [
|
||||
'name' => 'Maximale Anzahl',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'defaultValue' => 20,
|
||||
'title' => 'Maximale Anzahl an Episoden (max. 50)'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
private $currentSeries = '';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
// HTML von der Startseite laden
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
if (!$html) {
|
||||
returnServerError('Konnte SerienFans.org nicht laden');
|
||||
}
|
||||
|
||||
// Alle h3 und div.row Elemente direkt finden
|
||||
$allElements = $html->find('div.list h3, div.list div.row');
|
||||
|
||||
if (empty($allElements)) {
|
||||
returnServerError('Keine Updates gefunden');
|
||||
}
|
||||
|
||||
// Temporärer Speicher für Episoden (gruppiert nach Serie + Episode)
|
||||
$episodesGrouped = [];
|
||||
|
||||
// Durchlaufe alle Elemente
|
||||
foreach ($allElements as $element) {
|
||||
if ($element->tag === 'h3') {
|
||||
// Neuer Serien-Name
|
||||
$seriesLink = $element->find('a', 0);
|
||||
if ($seriesLink) {
|
||||
$this->currentSeries = trim($seriesLink->plaintext);
|
||||
}
|
||||
} elseif ($element->tag === 'div') {
|
||||
// Episode parsen und gruppieren
|
||||
$this->parseEpisode($element, $episodesGrouped);
|
||||
}
|
||||
}
|
||||
|
||||
// Gruppierte Episoden in finale Items konvertieren
|
||||
foreach ($episodesGrouped as $groupKey => $group) {
|
||||
$item = [
|
||||
'title' => $group['title'],
|
||||
'uri' => $group['uri'],
|
||||
'timestamp' => $group['timestamp'],
|
||||
'content' => $this->buildCombinedContent($group['coverUrl'], $group['releases'], $group['episodeInfo']),
|
||||
'author' => 'SerienFans',
|
||||
'categories' => $group['categories'],
|
||||
'enclosures' => $group['coverUrl'] ? [$group['coverUrl']] : [],
|
||||
'uid' => md5($groupKey)
|
||||
];
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
// Nach Timestamp sortieren (neueste zuerst)
|
||||
usort($this->items, function ($a, $b) {
|
||||
return $b['timestamp'] <=> $a['timestamp'];
|
||||
});
|
||||
|
||||
// Limit anwenden
|
||||
$limit = min((int)$this->getInput('limit'), 50);
|
||||
$this->items = array_slice($this->items, 0, $limit);
|
||||
}
|
||||
|
||||
private function parseEpisode($row, &$episodesGrouped)
|
||||
{
|
||||
// Zeit extrahieren
|
||||
$timeTag = $row->find('div.datime', 0);
|
||||
$uploadTime = $timeTag ? trim($timeTag->plaintext) : '';
|
||||
$timestamp = $this->parseUploadTime($uploadTime);
|
||||
|
||||
// Cover-Bild extrahieren
|
||||
$coverTag = $row->find('i.cover img', 0);
|
||||
$coverUrl = '';
|
||||
if ($coverTag && $coverTag->src) {
|
||||
$coverUrl = $coverTag->src;
|
||||
if (strpos($coverUrl, '/') === 0) {
|
||||
$coverUrl = 'https://serienfans.org' . $coverUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Release-Name und Link extrahieren
|
||||
$releaseLink = $row->find('div a', 0);
|
||||
if (!$releaseLink) {
|
||||
return; // Skip wenn kein Link
|
||||
}
|
||||
|
||||
$releaseName = trim($releaseLink->plaintext);
|
||||
$episodeUrl = $releaseLink->href;
|
||||
if ($episodeUrl && strpos($episodeUrl, '/') === 0) {
|
||||
$episodeUrl = 'https://serienfans.org' . $episodeUrl;
|
||||
}
|
||||
|
||||
// Season/Episode aus Release-Namen extrahieren
|
||||
$episodeInfo = $this->extractEpisodeInfo($releaseName);
|
||||
|
||||
// Titel: Nur Serien-Name
|
||||
$title = $this->currentSeries;
|
||||
|
||||
// Gruppierungs-Key: Serie + Season + Episode
|
||||
$groupKey = $this->currentSeries . '_S' . ($episodeInfo['season'] ?: '0') . 'E' . ($episodeInfo['episode'] ?: '0');
|
||||
|
||||
// Wenn Gruppe noch nicht existiert, erstelle sie
|
||||
if (!isset($episodesGrouped[$groupKey])) {
|
||||
$episodesGrouped[$groupKey] = [
|
||||
'title' => $title,
|
||||
'uri' => $episodeUrl ?: self::URI,
|
||||
'timestamp' => $timestamp,
|
||||
'coverUrl' => $coverUrl,
|
||||
'episodeInfo' => $episodeInfo,
|
||||
'releases' => [],
|
||||
'categories' => [$this->currentSeries]
|
||||
];
|
||||
}
|
||||
|
||||
// Füge Release zur Gruppe hinzu
|
||||
$episodesGrouped[$groupKey]['releases'][] = [
|
||||
'name' => $releaseName,
|
||||
'url' => $episodeUrl
|
||||
];
|
||||
|
||||
// Aktualisiere Timestamp auf den neuesten
|
||||
if ($timestamp > $episodesGrouped[$groupKey]['timestamp']) {
|
||||
$episodesGrouped[$groupKey]['timestamp'] = $timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractEpisodeInfo($releaseName)
|
||||
{
|
||||
$info = [
|
||||
'season' => null,
|
||||
'episode' => null,
|
||||
'quality' => '',
|
||||
'language' => ''
|
||||
];
|
||||
|
||||
// Season/Episode Pattern: S01E07, S1E7, etc.
|
||||
if (preg_match('/S(\d+)E(\d+)/i', $releaseName, $match)) {
|
||||
$info['season'] = (int)$match[1];
|
||||
$info['episode'] = (int)$match[2];
|
||||
}
|
||||
|
||||
// Qualität: 1080p, 720p, 2160p, etc.
|
||||
if (preg_match('/(\d{3,4}p)/i', $releaseName, $match)) {
|
||||
$info['quality'] = $match[1];
|
||||
}
|
||||
|
||||
// Sprache: GERMAN, DL (Dual Language), etc.
|
||||
if (preg_match('/GERMAN/i', $releaseName)) {
|
||||
$info['language'] = 'DE';
|
||||
} elseif (preg_match('/\bDL\b/i', $releaseName)) {
|
||||
$info['language'] = 'DE/EN';
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
private function buildCombinedContent($coverUrl, $releases, $episodeInfo)
|
||||
{
|
||||
$content = '';
|
||||
|
||||
// Cover-Bild
|
||||
if ($coverUrl) {
|
||||
$content .= '<img src="' . htmlspecialchars($coverUrl) . '" alt="Cover" /><br>';
|
||||
}
|
||||
|
||||
// Episode-Info
|
||||
if ($episodeInfo['season'] && $episodeInfo['episode']) {
|
||||
$content .= '<p><strong>Episode:</strong> S' .
|
||||
sprintf('%02d', $episodeInfo['season']) . 'E' .
|
||||
sprintf('%02d', $episodeInfo['episode']) . '</p>';
|
||||
}
|
||||
|
||||
// Releases
|
||||
if (!empty($releases)) {
|
||||
$content .= '<p><strong>Releases:</strong></p><ul>';
|
||||
foreach ($releases as $release) {
|
||||
$content .= '<li>' . htmlspecialchars($release['name']) . '</li>';
|
||||
}
|
||||
$content .= '</ul>';
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseUploadTime($timeString)
|
||||
{
|
||||
// Format: "HH:MM" z.B. "02:45"
|
||||
if (preg_match('/^(\d{1,2}):(\d{2})$/', $timeString, $match)) {
|
||||
$hours = (int)$match[1];
|
||||
$minutes = (int)$match[2];
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$timestamp = strtotime("$today $hours:$minutes:00");
|
||||
|
||||
// Wenn Timestamp in der Zukunft liegt (mehr als 1 Stunde), ist es von gestern
|
||||
if ($timestamp > time() + 3600) {
|
||||
$timestamp = strtotime("yesterday $hours:$minutes:00");
|
||||
}
|
||||
|
||||
return $timestamp;
|
||||
}
|
||||
|
||||
// Fallback: aktueller Zeitstempel
|
||||
return time();
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.google.com/s2/favicons?domain=serienfans.org&sz=32';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user