<?php declare(strict_types=1); namespace OCA\RePod\Core\EpisodeAction; use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionReader as CoreEpisodeActionReader; use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository; use OCA\RePod\Core\PodcastData\PodcastData; use OCA\RePod\Service\UserService; class EpisodeActionReader extends CoreEpisodeActionReader { public function __construct( private EpisodeActionRepository $episodeActionRepository, private UserService $userService ) {} /** * Base: https://github.com/pbek/nextcloud-nextpod/blob/main/lib/Core/EpisodeAction/EpisodeActionExtraData.php#L119. * Specs : https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification/blob/main/README.md. * * @return EpisodeActionExtraData[] * @throws \Exception if the XML data could not be parsed */ public function parseRssXml(string $xmlString, ?int $fetchedAtUnix = null): array { $episodes = []; $xml = new \SimpleXMLElement($xmlString); $channel = $xml->channel; $title = (string) $channel->title; // Find episode by url and add data for it /** @var \SimpleXMLElement $item */ foreach ($channel->item as $item) { $url = (string) $item->enclosure['url']; $type = (string) $item->enclosure['type']; $size = (int) $item->enclosure['length']; $guid = (string) $item->guid; $iTunesItemChildren = $item->children('itunes', true); $iTunesChannelChildren = $channel->children('itunes', true); // Get episode action $action = $this->episodeActionRepository->findByGuid($guid, $this->userService->getUserUID()); if ($action) { $url = $action->getEpisode(); } else { $action = $this->episodeActionRepository->findByEpisodeUrl($url, $this->userService->getUserUID()); } // Get episode name $name = (string) $item->title; // Get episode link $link = PodcastData::stringOrNull($item->link); // Get episode image $image = PodcastData::stringOrNull($item->image->url); if (!$image && $iTunesItemChildren) { $imageAttributes = $iTunesItemChildren->image->attributes(); $image = PodcastData::stringOrNull($imageAttributes ? (string) $imageAttributes->href : ''); } if (!$image) { $image = PodcastData::stringOrNull($channel->image->url); } if (!$image && $iTunesChannelChildren) { $imageAttributes = $iTunesChannelChildren->image->attributes(); $image = PodcastData::stringOrNull($imageAttributes ? (string) $imageAttributes->href : ''); } if (!$image) { preg_match('/<itunes:image\s+href="([^"]+)"/', $xmlString, $matches); $image = PodcastData::stringOrNull($matches[1]); } // Get episode description $itemContent = $item->children('content', true); if ($itemContent) { $description = PodcastData::stringOrNull($itemContent->encoded); } else { $description = PodcastData::stringOrNull($item->description); } if (!$description && $iTunesItemChildren) { $description = PodcastData::stringOrNull($iTunesItemChildren->summary); } // Remove tags $description = strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $description ?? '')); // Get episode duration if ($iTunesItemChildren) { $rawDuration = PodcastData::stringOrNull($iTunesItemChildren->duration); } else { $rawDuration = PodcastData::stringOrNull($item->duration); } $splitDuration = array_reverse(explode(':', $rawDuration ?? '')); $duration = (int) $splitDuration[0]; $duration += !empty($splitDuration[1]) ? (int) $splitDuration[1] * 60 : 0; $duration += !empty($splitDuration[2]) ? (int) $splitDuration[2] * 60 : 0; // Get episode pubDate $rawPubDate = PodcastData::stringOrNull($item->pubDate); $pubDate = $rawPubDate ? new \DateTime($rawPubDate) : null; $episodes[] = new EpisodeActionExtraData( $title, $url, $name, $link, $image, $description, $fetchedAtUnix ?? (new \DateTime())->getTimestamp(), $guid, $type, $size, $pubDate, $duration, $action ); } return $episodes; } }