refacto: Use custom PodcastData to fix redirections (close #33)
This commit is contained in:
parent
7375088700
commit
3d066d63c6
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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('/<itunes:image\s+href="([^"]+)"/', $xmlString, $matches);
|
||||
$image = $this->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;
|
||||
}
|
||||
}
|
||||
|
90
lib/Core/PodcastData/PodcastData.php
Normal file
90
lib/Core/PodcastData/PodcastData.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\RePod\Core\PodcastData;
|
||||
|
||||
use OCA\GPodderSync\Core\PodcastData\PodcastData as CorePodcastData;
|
||||
|
||||
class PodcastData extends CorePodcastData implements \JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
?string $title,
|
||||
?string $author,
|
||||
?string $link,
|
||||
?string $description,
|
||||
?string $imageUrl,
|
||||
int $fetchedAtUnix,
|
||||
?string $imageBlob = null,
|
||||
private ?string $atomLink
|
||||
) {
|
||||
parent::__construct(
|
||||
$title,
|
||||
$author,
|
||||
$link,
|
||||
$description,
|
||||
$imageUrl,
|
||||
$fetchedAtUnix,
|
||||
$imageBlob
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception if the XML data could not be parsed
|
||||
*/
|
||||
public static function parseRssXml(string $xmlString, ?int $fetchedAtUnix = null): PodcastData {
|
||||
$xml = new \SimpleXMLElement($xmlString);
|
||||
$channel = $xml->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;
|
||||
}
|
||||
}
|
@ -12,7 +12,8 @@
|
||||
:image-url="feed.imageUrl"
|
||||
:link="feed.link"
|
||||
:title="feed.title" />
|
||||
<Episodes v-if="feed" />
|
||||
<Episodes v-if="feed"
|
||||
:title="feed.title" />
|
||||
</NcAppContent>
|
||||
</template>
|
||||
|
||||
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user