diff --git a/lib/Controller/PodcastController.php b/lib/Controller/PodcastController.php index 17871e3..e3f34bc 100644 --- a/lib/Controller/PodcastController.php +++ b/lib/Controller/PodcastController.php @@ -4,10 +4,8 @@ declare(strict_types=1); namespace OCA\RePod\Controller; -use OCA\GPodderSync\Core\PodcastData\PodcastData; -use OCA\GPodderSync\Core\PodcastData\PodcastDataReader; use OCA\RePod\AppInfo\Application; -use OCA\RePod\Service\UserService; +use OCA\RePod\Core\PodcastData\PodcastData; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\JSONResponse; use OCP\Http\Client\IClientService; @@ -17,26 +15,16 @@ class PodcastController extends Controller { public function __construct( IRequest $request, - private IClientService $clientService, - private UserService $userService, - private PodcastDataReader $podcastDataReader + private IClientService $clientService ) { parent::__construct(Application::APP_ID, $request); } public function index(string $url): JSONResponse { - $podcast = $this->podcastDataReader->tryGetCachedPodcastData($url); - - if ($podcast) { - return new JSONResponse($podcast); - } - $client = $this->clientService->newClient(); $feed = $client->get($url); - $podcast = PodcastData::parseRssXml((string) $feed->getBody()); - $this->podcastDataReader->trySetCachedPodcastData($url, $podcast); - return new JSONResponse($podcast, $feed->getStatusCode()); + return new JSONResponse($podcast->toArrayWithExtras(), $feed->getStatusCode()); } } diff --git a/lib/Core/EpisodeAction/EpisodeActionExtraData.php b/lib/Core/EpisodeAction/EpisodeActionExtraData.php index e82acfd..431c344 100644 --- a/lib/Core/EpisodeAction/EpisodeActionExtraData.php +++ b/lib/Core/EpisodeAction/EpisodeActionExtraData.php @@ -13,7 +13,6 @@ use OCA\GPodderSync\Core\EpisodeAction\EpisodeAction; * @psalm-import-type EpisodeActionType from EpisodeAction * * @psalm-type EpisodeActionExtraDataType = array{ - * podcast: string, * url: ?string, * name: string, * link: ?string, @@ -31,7 +30,6 @@ use OCA\GPodderSync\Core\EpisodeAction\EpisodeAction; class EpisodeActionExtraData implements \JsonSerializable { public function __construct( - private string $podcast, private ?string $url, private string $name, private ?string $link, @@ -50,10 +48,6 @@ class EpisodeActionExtraData implements \JsonSerializable return $this->url ?? '/no episodeUrl/'; } - public function getPodcast(): string { - return $this->podcast; - } - public function getUrl(): ?string { return $this->url; } @@ -108,7 +102,6 @@ class EpisodeActionExtraData implements \JsonSerializable public function toArray(): array { return [ - 'podcast' => $this->podcast, 'url' => $this->url, 'name' => $this->name, 'link' => $this->link, diff --git a/lib/Core/EpisodeAction/EpisodeActionReader.php b/lib/Core/EpisodeAction/EpisodeActionReader.php index f41362d..e6d7519 100644 --- a/lib/Core/EpisodeAction/EpisodeActionReader.php +++ b/lib/Core/EpisodeAction/EpisodeActionReader.php @@ -4,21 +4,18 @@ declare(strict_types=1); namespace OCA\RePod\Core\EpisodeAction; -use OCA\GPodderSync\Core\EpisodeAction\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 +class EpisodeActionReader extends CoreEpisodeActionReader { public function __construct( private EpisodeActionRepository $episodeActionRepository, private UserService $userService ) {} - public function findByEpisodeUrl(string $episodeUrl): ?EpisodeAction { - return $this->episodeActionRepository->findByEpisodeUrl($episodeUrl, $this->userService->getUserUID()); - } - /** * 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. @@ -30,7 +27,6 @@ class EpisodeActionReader $episodes = []; $xml = new \SimpleXMLElement($xmlString); $channel = $xml->channel; - $podcast = (string) $channel->title; // Find episode by url and add data for it /** @var \SimpleXMLElement $item */ @@ -56,40 +52,40 @@ class EpisodeActionReader $name = (string) $item->title; // Get episode link - $link = $this->stringOrNull($item->link); + $link = PodcastData::stringOrNull($item->link); // Get episode image - $image = $this->stringOrNull($item->image->url); + $image = PodcastData::stringOrNull($item->image->url); if (!$image && $iTunesItemChildren) { $imageAttributes = $iTunesItemChildren->image->attributes(); - $image = $this->stringOrNull($imageAttributes ? (string) $imageAttributes->href : ''); + $image = PodcastData::stringOrNull($imageAttributes ? (string) $imageAttributes->href : ''); } if (!$image) { - $image = $this->stringOrNull($channel->image->url); + $image = PodcastData::stringOrNull($channel->image->url); } if (!$image && $iTunesChannelChildren) { $imageAttributes = $iTunesChannelChildren->image->attributes(); - $image = $this->stringOrNull($imageAttributes ? (string) $imageAttributes->href : ''); + $image = PodcastData::stringOrNull($imageAttributes ? (string) $imageAttributes->href : ''); } if (!$image) { preg_match('/stringOrNull($matches[1]); + $image = PodcastData::stringOrNull($matches[1]); } // Get episode description $itemContent = $item->children('content', true); if ($itemContent) { - $description = $this->stringOrNull($itemContent->encoded); + $description = PodcastData::stringOrNull($itemContent->encoded); } else { - $description = $this->stringOrNull($item->description); + $description = PodcastData::stringOrNull($item->description); } if (!$description && $iTunesItemChildren) { - $description = $this->stringOrNull($iTunesItemChildren->summary); + $description = PodcastData::stringOrNull($iTunesItemChildren->summary); } // Remove tags @@ -97,9 +93,9 @@ class EpisodeActionReader // Get episode duration if ($iTunesItemChildren) { - $rawDuration = $this->stringOrNull($iTunesItemChildren->duration); + $rawDuration = PodcastData::stringOrNull($iTunesItemChildren->duration); } else { - $rawDuration = $this->stringOrNull($item->duration); + $rawDuration = PodcastData::stringOrNull($item->duration); } $splitDuration = array_reverse(explode(':', $rawDuration ?? '')); @@ -108,11 +104,10 @@ class EpisodeActionReader $duration += !empty($splitDuration[2]) ? (int) $splitDuration[2] * 60 : 0; // Get episode pubDate - $rawPubDate = $this->stringOrNull($item->pubDate); + $rawPubDate = PodcastData::stringOrNull($item->pubDate); $pubDate = $rawPubDate ? new \DateTime($rawPubDate) : null; $episodes[] = new EpisodeActionExtraData( - $podcast, $url, $name, $link, @@ -130,15 +125,4 @@ class EpisodeActionReader return $episodes; } - - /** - * @param null|\SimpleXMLElement|string $value - */ - private function stringOrNull($value): ?string { - if ($value) { - return (string) $value; - } - - return null; - } } diff --git a/lib/Core/PodcastData/PodcastData.php b/lib/Core/PodcastData/PodcastData.php new file mode 100644 index 0000000..e475142 --- /dev/null +++ b/lib/Core/PodcastData/PodcastData.php @@ -0,0 +1,90 @@ +channel; + + return new PodcastData( + self::stringOrNull($channel->title), + self::getXPathContent($xml, '/rss/channel/itunes:author'), + self::stringOrNull($channel->link), + self::stringOrNull($channel->description), + self::getXPathContent($xml, '/rss/channel/image/url') + ?? self::getXPathAttribute($xml, '/rss/channel/itunes:image/@href'), + $fetchedAtUnix ?? (new \DateTime())->getTimestamp(), + null, + self::getXPathContent($xml, '/rss/channel/atom:link/@href') + ); + } + + /** + * @param null|\SimpleXMLElement|string $value + */ + public static function stringOrNull($value): ?string { + if ($value) { + return (string) $value; + } + + return null; + } + + public function getAtomLink(): ?string { + return $this->atomLink; + } + + public function toArrayWithExtras() { + return array_merge(parent::toArray(), [ + 'atomLink' => $this->atomLink, + ]); + } + + private static function getXPathContent(\SimpleXMLElement $xml, string $xpath): ?string { + $match = $xml->xpath($xpath); + if ($match) { + return (string) $match[0]; + } + + return null; + } + + private static function getXPathAttribute(\SimpleXMLElement $xml, string $xpath): ?string { + $match = $xml->xpath($xpath); + if ($match) { + return (string) $match[0][0]; + } + + return null; + } +} diff --git a/src/views/Feed.vue b/src/views/Feed.vue index 4416616..75f32f8 100644 --- a/src/views/Feed.vue +++ b/src/views/Feed.vue @@ -12,7 +12,8 @@ :image-url="feed.imageUrl" :link="feed.link" :title="feed.title" /> - + @@ -24,6 +25,7 @@ import Episodes from '../components/Feed/Episodes.vue' import Loading from '../components/Atoms/Loading.vue' import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' +import { toUrl } from '../utils/url.js' export default { name: 'Feed', @@ -50,6 +52,11 @@ export default { async mounted() { try { const podcastData = await axios.get(generateUrl('/apps/repod/podcast?url={url}', { url: this.url })) + + if (podcastData.data.atomLink !== this.url) { + this.$router.push(toUrl(podcastData.data.atomLink)) + } + this.feed = podcastData.data } catch (e) { this.failed = true