1
0
Files
Bridges/JoynBridge.php

230 lines
8.1 KiB
PHP

<?php
class JoynBridge extends BridgeAbstract {
const NAME = 'Joyn.de Serien RSS';
const URI = 'https://www.joyn.de/';
const DESCRIPTION = 'RSS-Feed für Serien von Joyn.de';
const CACHE_TIMEOUT = 21600; // 6h
const MAINTAINER = 'Akamaru, Claude';
const ICON = 'https://www.joyn.de/favicon.ico';
const PARAMETERS = [
[
'series_id' => [
'name' => 'Serien-ID',
'type' => 'text',
'title' => 'ID der Serie (z.B. auf-streife)',
'required' => true
]
]
];
private $feedName = null;
private function makeGraphQLRequest($query, $variables = []) {
$url = 'https://api.joyn.de/graphql';
$headers = [
'Content-Type: application/json',
'x-api-key: 4f0fd9f18abbe3cf0e87fdb556bc39c8',
'Joyn-Platform: web'
];
$payload = json_encode([
'query' => $query,
'variables' => $variables
]);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => implode("\r\n", $headers) . "\r\n",
'content' => $payload
]
]);
$response = file_get_contents($url, false, $context);
if ($response === false) {
returnServerError('GraphQL request failed');
}
$data = json_decode($response, true);
if (!$data) {
returnServerError('Could not parse GraphQL response');
}
if (isset($data['errors'])) {
returnServerError('GraphQL errors: ' . json_encode($data['errors']));
}
return $data['data'] ?? null;
}
public function collectData() {
$series_id = $this->getInput('series_id');
if (empty($series_id)) {
return;
}
$path = '/serien/' . $series_id;
// First GraphQL request: Get all seasons to find highest available number
$checkQuery = '
query ($path: String!) {
page(path: $path) {
... on SeriesPage {
series {
numberOfSeasons
seasons {
number
numberOfEpisodes
episodes(first: 1) {
number
}
}
}
}
}
}
';
$checkData = $this->makeGraphQLRequest($checkQuery, ['path' => $path]);
if (!$checkData || !isset($checkData['page']['series'])) {
returnServerError('Could not fetch series information');
}
$series = $checkData['page']['series'];
$numberOfSeasons = $series['numberOfSeasons'] ?? 0;
$seasons = $series['seasons'] ?? [];
if ($numberOfSeasons === 0 || empty($seasons)) {
returnServerError('No seasons found for this series');
}
// Find the highest available season number (delisted seasons create gaps)
$seasonNumbers = array_map(function($season) {
return $season['number'] ?? 0;
}, $seasons);
$highestSeasonNumber = max($seasonNumbers);
// Find first and last seasons by their actual position in the array
$firstSeason = $seasons[0] ?? null;
$lastSeason = $seasons[count($seasons) - 1] ?? null;
if (!$firstSeason || !$lastSeason) {
returnServerError('Could not find seasons');
}
$firstSeasonNumber = $firstSeason['number'] ?? 1;
$lastSeasonNumber = $lastSeason['number'] ?? 1;
$firstEpisodes = $firstSeason['episodes'] ?? [];
$firstEpisodeNumber = !empty($firstEpisodes) ? ($firstEpisodes[0]['number'] ?? 1) : 1;
// Check if series is chronologically sorted (newest first)
// If first season has higher number than last season, it's sorted newest first
$isChronological = $firstSeasonNumber > $lastSeasonNumber;
$seasonOffset = 0;
$episodeOffset = 0;
$seasonLimit = 1;
$episodeLimit = 10;
if (!$isChronological) {
// Series is sorted oldest first, use offset to get latest (last season in array)
$seasonOffset = count($seasons) - 1;
$numberOfEpisodesInLastSeason = $lastSeason['numberOfEpisodes'] ?? 0;
$episodeOffset = max(0, $numberOfEpisodesInLastSeason - 10);
}
// Second GraphQL request: Get the latest episodes
$episodeQuery = '
query ($path: String!) {
page(path: $path) {
... on SeriesPage {
series {
id
title
description
numberOfSeasons
seasons(' . ($seasonOffset > 0 ? 'offset: ' . $seasonOffset : 'first: ' . $seasonLimit) . ') {
id
title
number
numberOfEpisodes
episodes(' . ($episodeOffset > 0 ? 'offset: ' . $episodeOffset : 'first: ' . $episodeLimit) . ') {
id
number
airdate
title
description
path
}
}
}
}
}
}
';
$episodeData = $this->makeGraphQLRequest($episodeQuery, ['path' => $path]);
if (!$episodeData || !isset($episodeData['page']['series'])) {
returnServerError('Could not fetch episode information');
}
$seriesData = $episodeData['page']['series'];
$seriesTitle = $seriesData['title'] ?? '';
// Set feed name
$this->feedName = $seriesTitle . ' | Joyn';
$seasons = $seriesData['seasons'] ?? [];
if (empty($seasons)) {
returnServerError('No season data found');
}
$season = $seasons[0];
$seasonNumber = $season['number'] ?? 1;
$episodes = $season['episodes'] ?? [];
// Reverse episodes array only if series is sorted oldest first
if (!$isChronological) {
$episodes = array_reverse($episodes);
}
foreach ($episodes as $episode) {
$episodeId = $episode['id'] ?? '';
$episodeNumber = $episode['number'] ?? 1;
$title = $episode['title'] ?? '';
$description = $episode['description'] ?? '';
$airdate = $episode['airdate'] ?? null;
$episodePath = $episode['path'] ?? '';
$item = [];
$seasonNum = str_pad($seasonNumber, 2, '0', STR_PAD_LEFT);
$epNum = str_pad($episodeNumber, 2, '0', STR_PAD_LEFT);
$epCode = 'S' . $seasonNum . 'E' . $epNum;
$item['title'] = sprintf('%s %s "%s"', $seriesTitle, $epCode, $title);
$item['content'] = '<p>' . htmlspecialchars($description) . '</p>';
$item['uid'] = $episodeId;
$item['author'] = 'Joyn';
if ($airdate) {
$item['timestamp'] = $airdate;
}
if ($episodePath) {
$item['uri'] = 'https://www.joyn.de' . $episodePath;
} else {
$item['uri'] = 'https://www.joyn.de' . $path;
}
$this->items[] = $item;
}
}
public function getName() {
if (!is_null($this->feedName)) {
return $this->feedName;
}
return parent::getName();
}
}