[ '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 number of seasons and episodes $seasonQuery = ' query ($path: String!) { page(path: $path) { ... on SeriesPage { series { numberOfSeasons seasons { numberOfEpisodes } } } } } '; $seasonData = $this->makeGraphQLRequest($seasonQuery, ['path' => $path]); if (!$seasonData || !isset($seasonData['page']['series'])) { returnServerError('Could not fetch series information'); } $series = $seasonData['page']['series']; $numberOfSeasons = $series['numberOfSeasons'] ?? 0; $seasons = $series['seasons'] ?? []; if ($numberOfSeasons === 0 || empty($seasons)) { returnServerError('No seasons found for this series'); } // Get the last season (index: numberOfSeasons - 1) $lastSeasonIndex = $numberOfSeasons - 1; $lastSeason = $seasons[$lastSeasonIndex] ?? null; if (!$lastSeason) { returnServerError('Could not find last season'); } $numberOfEpisodesInLastSeason = $lastSeason['numberOfEpisodes'] ?? 0; if ($numberOfEpisodesInLastSeason === 0) { returnServerError('No episodes found in last season'); } // Calculate offset to get last 10 episodes (or all if less than 10) $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(offset: ' . $lastSeasonIndex . ') { id title number numberOfEpisodes episodes(offset: ' . $episodeOffset . ') { 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 to show newest first $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'] = '

' . htmlspecialchars($description) . '

'; $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(); } }