1
0

Neu: FilmFans Bridge

This commit is contained in:
Akamaru
2025-12-27 17:53:58 +01:00
parent 167e642f47
commit 4f03da9447
2 changed files with 219 additions and 0 deletions

209
FilmFansBridge.php Normal file
View File

@@ -0,0 +1,209 @@
<?php
declare(strict_types=1);
class FilmFansBridge extends BridgeAbstract
{
const NAME = 'FilmFans';
const URI = 'https://filmfans.org/';
const DESCRIPTION = 'Gibt die neuesten Uploads von FilmFans 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 Uploads (max. 50)'
]
]
];
public function collectData()
{
// HTML von der Startseite laden
$html = getSimpleHTMLDOM(self::URI);
if (!$html) {
returnServerError('Konnte FilmFans.org nicht laden');
}
// Upload-Einträge finden
$entries = $html->find('div.sra');
if (empty($entries)) {
returnServerError('Keine Uploads gefunden');
}
// Jeden Eintrag parsen
foreach ($entries as $entry) {
// Upload-Zeit aus lsf-icon timed extrahieren
$timeTag = $entry->find('span.lsf-icon.timed', 0);
$uploadTime = $timeTag ? trim($timeTag->plaintext) : '';
// Timestamp berechnen (HH:MM Format zu Unix timestamp)
$timestamp = $this->parseUploadTime($uploadTime);
// h2 Tag finden
$h2 = $entry->find('h2', 0);
if (!$h2) {
continue;
}
// Titel extrahieren (nur der Text vor dem <i> Tag)
$titleText = '';
foreach ($h2->nodes as $node) {
if ($node->tag === 'text') {
$titleText .= $node->outertext;
} elseif ($node->tag === 'i') {
break; // Stop bei Jahr
}
}
$title = trim($titleText);
// Jahr aus <i> Tag extrahieren
$yearTag = $h2->find('i', 0);
$year = $yearTag ? trim($yearTag->plaintext, '()') : '';
// Movie URL extrahieren (erstes <a> Tag im Entry)
$linkTag = $entry->find('a', 0);
$movieUrl = $linkTag ? $linkTag->href : '';
if ($movieUrl && strpos($movieUrl, '/') === 0) {
$movieUrl = 'https://filmfans.org' . $movieUrl;
}
// Cover-Bild extrahieren (aus i.cover)
$coverTag = $entry->find('i.cover img', 0);
$coverUrl = '';
if ($coverTag && $coverTag->src) {
$coverUrl = $coverTag->src;
if (strpos($coverUrl, '/') === 0) {
$coverUrl = 'https://filmfans.org' . $coverUrl;
}
}
// Beschreibung extrahieren
$descTag = $entry->find('p.description', 0);
$description = $descTag ? trim($descTag->plaintext) : '';
// Release-Namen sammeln (alle <a> Tags innerhalb h2 > span)
$releases = [];
$releaseSpan = $h2->find('span', 0);
if ($releaseSpan) {
foreach ($releaseSpan->find('a') as $releaseLink) {
$releaseName = trim($releaseLink->plaintext);
if ($releaseName) {
$releases[] = $releaseName;
}
}
}
// Sprachen extrahieren
$languages = [];
foreach ($entry->find('span.audiotag img') as $langImg) {
if ($langImg->src) {
// /images/DE.svg → DE
if (preg_match('#/images/([A-Z]{2})\.svg#', $langImg->src, $match)) {
$languages[] = $match[1];
}
}
}
// Genres extrahieren
$genres = [];
foreach ($entry->find('a.genre') as $genreLink) {
$genre = trim($genreLink->plaintext);
if ($genre) {
$genres[] = $genre;
}
}
// Item erstellen
$item = [
'title' => $title . ($year ? " ($year)" : ''),
'uri' => $movieUrl ?: self::URI,
'timestamp' => $timestamp,
'content' => $this->buildContent($coverUrl, $description, $releases, $languages, $genres),
'author' => 'FilmFans',
'categories' => $genres,
'enclosures' => $coverUrl ? [$coverUrl] : [],
'uid' => md5($movieUrl)
];
$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 parseUploadTime($timeString)
{
// Format: "HH:MM" z.B. "13:57"
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();
}
private function buildContent($coverUrl, $description, $releases, $languages, $genres)
{
$content = '';
// Cover-Bild
if ($coverUrl) {
$content .= '<img src="' . htmlspecialchars($coverUrl) . '" alt="Cover" /><br>';
}
// Beschreibung
if ($description) {
$content .= '<p><strong>Beschreibung:</strong> ' . htmlspecialchars($description) . '</p>';
}
// Sprachen
if (!empty($languages)) {
$content .= '<p><strong>Sprachen:</strong> ' . implode(', ', $languages) . '</p>';
}
// Genres
if (!empty($genres)) {
$content .= '<p><strong>Genres:</strong> ' . implode(', ', $genres) . '</p>';
}
// Releases
if (!empty($releases)) {
$content .= '<p><strong>Releases:</strong></p><ul>';
foreach ($releases as $release) {
$content .= '<li>' . htmlspecialchars($release) . '</li>';
}
$content .= '</ul>';
}
return $content;
}
public function getIcon()
{
return 'https://www.google.com/s2/favicons?domain=filmfans.org&sz=32';
}
}

View File

@@ -124,6 +124,16 @@ Diese Sammlung enthält verschiedene Bridge-Implementierungen für RSS-Bridge, u
### [EverSD News Bridge](https://bridge.ponywave.de/#bridge-EverSDBridge) (Von Akamaru)
- **Beschreibung**: EverSD News und Changelog.
### [FilmFans Bridge](https://bridge.ponywave.de/#bridge-FilmFansBridge) (Von Akamaru)
- **Beschreibung**: Gibt die neuesten Film-Uploads von FilmFans zurück
- **Parameter**:
- **Limit** (optional): Maximale Anzahl an Uploads (Standard: 20, max: 50)
- **Hinweise**:
- Zeigt Filmtitel, Cover-Bilder, Beschreibungen und Release-Namen
- Alle Releases eines Films werden im Content aufgelistet
- Extrahiert Sprachen und Genres
- Sortiert nach Upload-Zeit (neueste zuerst)
### [Florida TV Bridge](https://bridge.ponywave.de/#bridge-FloridaTVBridge) (Von Akamaru)
- **Beschreibung**: Neueste News von FloridaTV Entertainment.