Update the Podcast RSS feed to write raw XML instead of using an RSS library; add iTunes season/episode.
This commit is contained in:
parent
ba4a71cd98
commit
d03dc1f277
|
@ -52,7 +52,6 @@
|
|||
"league/oauth2-client": "^2.6",
|
||||
"league/plates": "^3.1",
|
||||
"lstrojny/fxmlrpc": "dev-master",
|
||||
"marcw/rss-writer": "^0.4.0",
|
||||
"matomo/device-detector": "^6",
|
||||
"mezzio/mezzio-session": "^1.3",
|
||||
"mezzio/mezzio-session-cache": "^1.7",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "5bca0c641ba21645d05ab830394898ae",
|
||||
"content-hash": "77d8e4e6837ec47f02ea21d3f3cb767e",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
|
@ -3505,64 +3505,6 @@
|
|||
},
|
||||
"time": "2023-08-22T06:06:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "marcw/rss-writer",
|
||||
"version": "0.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/marcw/rss-writer.git",
|
||||
"reference": "4bbd63aea62246fe43bec589a1e8bdda2f4ef219"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/marcw/rss-writer/zipball/4bbd63aea62246fe43bec589a1e8bdda2f4ef219",
|
||||
"reference": "4bbd63aea62246fe43bec589a1e8bdda2f4ef219",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-xmlwriter": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.4",
|
||||
"symfony/debug": "^3.1",
|
||||
"symfony/http-foundation": "^3.1",
|
||||
"symfony/validator": "^3.1",
|
||||
"symfony/var-dumper": "^3.1"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/http-foundation": "Enable streaming RSS response",
|
||||
"symfony/validator": ""
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MarcW\\RssWriter\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Marc Weistroff",
|
||||
"email": "marc@weistroff.net"
|
||||
}
|
||||
],
|
||||
"description": "A simple yet powerful RSS2 feed writer with RSS extensions support (like iTunes podcast tags)",
|
||||
"keywords": [
|
||||
"feed",
|
||||
"podcast",
|
||||
"podcasting",
|
||||
"rss",
|
||||
"rss2"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/marcw/rss-writer/issues",
|
||||
"source": "https://github.com/marcw/rss-writer/tree/master"
|
||||
},
|
||||
"time": "2017-04-01T11:53:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "matomo/device-detector",
|
||||
"version": "6.3.0",
|
||||
|
|
|
@ -7,32 +7,15 @@ namespace App\Controller\Frontend\PublicPages;
|
|||
use App\Controller\SingleActionInterface;
|
||||
use App\Entity\ApiGenerator\PodcastApiGenerator;
|
||||
use App\Entity\ApiGenerator\PodcastEpisodeApiGenerator;
|
||||
use App\Entity\Podcast;
|
||||
use App\Entity\PodcastCategory;
|
||||
use App\Entity\PodcastEpisode;
|
||||
use App\Exception\NotFoundException;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Rss\PodcastNamespaceWriter;
|
||||
use App\Xml\Writer;
|
||||
use Carbon\CarbonImmutable;
|
||||
use DateTime;
|
||||
use MarcW\RssWriter\Extension\Atom\AtomLink;
|
||||
use MarcW\RssWriter\Extension\Atom\AtomWriter;
|
||||
use MarcW\RssWriter\Extension\Core\Category as RssCategory;
|
||||
use MarcW\RssWriter\Extension\Core\Channel as RssChannel;
|
||||
use MarcW\RssWriter\Extension\Core\CoreWriter;
|
||||
use MarcW\RssWriter\Extension\Core\Enclosure as RssEnclosure;
|
||||
use MarcW\RssWriter\Extension\Core\Guid as RssGuid;
|
||||
use MarcW\RssWriter\Extension\Core\Image as RssImage;
|
||||
use MarcW\RssWriter\Extension\Core\Item as RssItem;
|
||||
use MarcW\RssWriter\Extension\Itunes\ItunesChannel;
|
||||
use MarcW\RssWriter\Extension\Itunes\ItunesItem;
|
||||
use MarcW\RssWriter\Extension\Itunes\ItunesOwner;
|
||||
use MarcW\RssWriter\Extension\Itunes\ItunesWriter;
|
||||
use MarcW\RssWriter\Extension\Slash\Slash;
|
||||
use MarcW\RssWriter\Extension\Slash\SlashWriter;
|
||||
use MarcW\RssWriter\Extension\Sy\Sy;
|
||||
use MarcW\RssWriter\Extension\Sy\SyWriter;
|
||||
use MarcW\RssWriter\RssWriter;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
final class PodcastFeedAction implements SingleActionInterface
|
||||
|
@ -62,33 +45,74 @@ final class PodcastFeedAction implements SingleActionInterface
|
|||
// Fetch podcast API feed.
|
||||
$podcastApi = $this->podcastApiGenerator->__invoke($podcast, $request);
|
||||
|
||||
$channel->setTitle($podcastApi->title);
|
||||
$channel->setDescription($podcastApi->description);
|
||||
$channel->setLink($podcastApi->link ?? $podcastApi->links['self']);
|
||||
$channel->setLanguage($podcastApi->language);
|
||||
$now = CarbonImmutable::now($station->getTimezoneObject());
|
||||
|
||||
$channel->setCategories(
|
||||
$podcast->getCategories()->map(
|
||||
$rss = [
|
||||
'@xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd',
|
||||
'@xmlns:sy' => 'http://purl.org/rss/1.0/modules/syndication/',
|
||||
'@xmlns:slash' => 'http://purl.org/rss/1.0/modules/slash/',
|
||||
'@xmlns:atom' => 'http://www.w3.org/2005/Atom',
|
||||
'@xmlns:podcast' => 'https://podcastindex.org/namespace/1.0',
|
||||
'@version' => '2.0',
|
||||
];
|
||||
|
||||
$channel = [
|
||||
'title' => $podcastApi->title,
|
||||
'link' => $podcastApi->link ?? $podcastApi->links['self'],
|
||||
'description' => $podcastApi->description,
|
||||
'language' => $podcastApi->language,
|
||||
'lastBuildDate' => $now->toRssString(),
|
||||
'category' => $podcast->getCategories()->map(
|
||||
function (PodcastCategory $podcastCategory) {
|
||||
$rssCategory = new RssCategory();
|
||||
if (null === $podcastCategory->getSubTitle()) {
|
||||
$rssCategory->setTitle($podcastCategory->getTitle());
|
||||
} else {
|
||||
$rssCategory->setTitle($podcastCategory->getSubTitle());
|
||||
}
|
||||
return $rssCategory;
|
||||
return (null === $podcastCategory->getSubTitle())
|
||||
? $podcastCategory->getTitle()
|
||||
: $podcastCategory->getSubTitle();
|
||||
}
|
||||
)->getValues()
|
||||
);
|
||||
)->getValues(),
|
||||
'ttl' => 5,
|
||||
'image' => [
|
||||
'url' => $podcastApi->art,
|
||||
'title' => $podcastApi->title,
|
||||
],
|
||||
'itunes:author' => $podcastApi->author,
|
||||
'itunes:owner' => [],
|
||||
'itunes:image' => [
|
||||
'@href' => $podcastApi->art,
|
||||
],
|
||||
'itunes:explicit' => 'false',
|
||||
'itunes:category' => $podcast->getCategories()->map(
|
||||
function (PodcastCategory $podcastCategory) {
|
||||
return (null === $podcastCategory->getSubTitle())
|
||||
? [
|
||||
'@text' => $podcastCategory->getTitle(),
|
||||
] : [
|
||||
'@text' => $podcastCategory->getTitle(),
|
||||
'itunes:category' => [
|
||||
'@text' => $podcastCategory->getSubTitle(),
|
||||
],
|
||||
];
|
||||
}
|
||||
)->getValues(),
|
||||
'atom:link' => [
|
||||
'@rel' => 'self',
|
||||
'@type' => 'application/rss+xml',
|
||||
'@href' => (string)$request->getUri(),
|
||||
],
|
||||
'item' => [],
|
||||
];
|
||||
|
||||
$rssImage = new RssImage();
|
||||
$rssImage->setTitle($podcastApi->title);
|
||||
$rssImage->setUrl($podcastApi->art);
|
||||
if (null !== $podcastApi->link) {
|
||||
$rssImage->setLink($podcastApi->link);
|
||||
$channel['image']['link'] = $podcastApi->link;
|
||||
}
|
||||
|
||||
$channel->setImage($rssImage);
|
||||
if (empty($podcastApi->author) && empty($podcastApi->email)) {
|
||||
unset($channel['itunes:owner']);
|
||||
} else {
|
||||
$channel['itunes:owner'] = [
|
||||
'itunes:name' => $podcastApi->author,
|
||||
'itunes:email' => $podcastApi->email,
|
||||
];
|
||||
}
|
||||
|
||||
// Iterate through episodes.
|
||||
$hasPublishedEpisode = false;
|
||||
|
@ -105,53 +129,21 @@ final class PodcastFeedAction implements SingleActionInterface
|
|||
$hasExplicitEpisode = true;
|
||||
}
|
||||
|
||||
$channel->addItem($this->buildItemForEpisode($episode, $request));
|
||||
$channel['item'][] = $this->buildItemForEpisode($episode, $request);
|
||||
}
|
||||
|
||||
if (!$hasPublishedEpisode) {
|
||||
throw NotFoundException::podcast();
|
||||
}
|
||||
|
||||
$itunesChannel = new ItunesChannel();
|
||||
$itunesChannel->setExplicit($hasExplicitEpisode);
|
||||
$itunesChannel->setImage($rssImage->getUrl());
|
||||
$itunesChannel->setCategories(
|
||||
$podcast->getCategories()->map(
|
||||
function (PodcastCategory $podcastCategory) {
|
||||
return (null === $podcastCategory->getSubTitle())
|
||||
? $podcastCategory->getTitle()
|
||||
: [
|
||||
$podcastCategory->getTitle(),
|
||||
$podcastCategory->getSubTitle(),
|
||||
];
|
||||
}
|
||||
)->getValues()
|
||||
);
|
||||
if ($hasExplicitEpisode) {
|
||||
$channel['itunes:explicit'] = 'true';
|
||||
}
|
||||
|
||||
$itunesChannel->setOwner($this->buildItunesOwner($podcast));
|
||||
$itunesChannel->setAuthor($podcast->getAuthor());
|
||||
|
||||
$channel->addExtension($itunesChannel);
|
||||
$channel->addExtension(new Sy());
|
||||
$channel->addExtension(new Slash());
|
||||
$channel->addExtension(
|
||||
(new AtomLink())
|
||||
->setRel('self')
|
||||
->setHref((string)$request->getUri())
|
||||
->setType('application/rss+xml')
|
||||
);
|
||||
|
||||
$rssWriter = new RssWriter(null, [
|
||||
new CoreWriter(),
|
||||
new ItunesWriter(),
|
||||
new SyWriter(),
|
||||
new SlashWriter(),
|
||||
new AtomWriter(),
|
||||
new PodcastNamespaceWriter(),
|
||||
], true);
|
||||
$rss['channel'] = $channel;
|
||||
|
||||
$response->getBody()->write(
|
||||
$rssWriter->writeChannel($channel)
|
||||
Writer::toString($rss, 'rss')
|
||||
);
|
||||
|
||||
return $response
|
||||
|
@ -159,48 +151,45 @@ final class PodcastFeedAction implements SingleActionInterface
|
|||
->withHeader('X-Robots-Tag', 'index, nofollow');
|
||||
}
|
||||
|
||||
private function buildItemForEpisode(PodcastEpisode $episode, ServerRequest $request): RssItem
|
||||
private function buildItemForEpisode(PodcastEpisode $episode, ServerRequest $request): array
|
||||
{
|
||||
$station = $request->getStation();
|
||||
|
||||
$episodeApi = $this->episodeApiGenerator->__invoke($episode, $request);
|
||||
|
||||
$rssItem = new RssItem();
|
||||
$publishedAt = CarbonImmutable::createFromTimestamp($episodeApi->publish_at, $station->getTimezoneObject());
|
||||
|
||||
$rssItem->setGuid((new RssGuid())->setGuid($episodeApi->id));
|
||||
$rssItem->setTitle($episodeApi->title);
|
||||
$rssItem->setDescription($episodeApi->description);
|
||||
$rssItem->setLink($episodeApi->link ?? $episodeApi->links['self']);
|
||||
|
||||
$rssItem->setPubDate((new DateTime())->setTimestamp($episode->getPublishAt()));
|
||||
|
||||
$rssEnclosure = new RssEnclosure();
|
||||
$rssEnclosure->setUrl($episodeApi->links['download']);
|
||||
$item = [
|
||||
'title' => $episodeApi->title,
|
||||
'link' => $episodeApi->link ?? $episodeApi->links['self'],
|
||||
'description' => $episodeApi->description,
|
||||
'enclosure' => [
|
||||
'@url' => $episodeApi->links['download'],
|
||||
],
|
||||
'guid' => [
|
||||
'@isPermaLink' => 'false',
|
||||
'_' => $episodeApi->id,
|
||||
],
|
||||
'pubDate' => $publishedAt->toRssString(),
|
||||
'itunes:image' => [
|
||||
'@href' => $episodeApi->art,
|
||||
],
|
||||
'itunes:explicit' => $episodeApi->explicit ? 'true' : 'false',
|
||||
];
|
||||
|
||||
$podcastMedia = $episode->getMedia();
|
||||
if (null !== $podcastMedia) {
|
||||
$rssEnclosure->setType($podcastMedia->getMimeType());
|
||||
$rssEnclosure->setLength($podcastMedia->getLength());
|
||||
}
|
||||
$rssItem->setEnclosure($rssEnclosure);
|
||||
|
||||
$rssItem->addExtension(
|
||||
(new ItunesItem())
|
||||
->setExplicit($episode->getExplicit())
|
||||
->setImage($episodeApi->art)
|
||||
);
|
||||
|
||||
return $rssItem;
|
||||
}
|
||||
|
||||
private function buildItunesOwner(Podcast $podcast): ?ItunesOwner
|
||||
{
|
||||
if (empty($podcast->getAuthor()) && empty($podcast->getEmail())) {
|
||||
return null;
|
||||
$item['enclosure']['@length'] = $podcastMedia->getLength();
|
||||
$item['enclosure']['@type'] = $podcastMedia->getMimeType();
|
||||
}
|
||||
|
||||
$itunesOwner = new ItunesOwner();
|
||||
$itunesOwner->setName($podcast->getAuthor());
|
||||
$itunesOwner->setEmail($podcast->getEmail());
|
||||
if (null !== $episodeApi->season_number) {
|
||||
$item['itunes:season'] = (string)$episodeApi->season_number;
|
||||
}
|
||||
if (null !== $episodeApi->episode_number) {
|
||||
$item['itunes:episode'] = (string)$episodeApi->episode_number;
|
||||
}
|
||||
|
||||
return $itunesOwner;
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue