1
0

Neu: SerienFans Bridge

This commit is contained in:
Akamaru
2025-12-27 17:54:12 +01:00
parent 4f03da9447
commit 9e57efe2b2
2 changed files with 240 additions and 0 deletions

View File

@@ -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
View 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';
}
}