refacto: remove atomlink that leads to weird bugs
All checks were successful
repod / xml (push) Successful in 22s
repod / php (push) Successful in 56s
repod / nodejs (push) Successful in 2m5s
repod / release (push) Has been skipped

This commit is contained in:
Michel Roux 2024-01-16 14:21:35 +01:00
parent c48432c0b5
commit 540c5df7e5
4 changed files with 35 additions and 127 deletions

View File

@ -4,8 +4,9 @@ declare(strict_types=1);
namespace OCA\RePod\Controller; namespace OCA\RePod\Controller;
use OCA\GPodderSync\Core\PodcastData\PodcastData;
use OCA\GPodderSync\Core\PodcastData\PodcastDataReader;
use OCA\RePod\AppInfo\Application; use OCA\RePod\AppInfo\Application;
use OCA\RePod\Core\PodcastData\PodcastData;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\JSONResponse;
use OCP\Http\Client\IClientService; use OCP\Http\Client\IClientService;
@ -15,16 +16,24 @@ class PodcastController extends Controller
{ {
public function __construct( public function __construct(
IRequest $request, IRequest $request,
private IClientService $clientService private IClientService $clientService,
private PodcastDataReader $podcastDataReader
) { ) {
parent::__construct(Application::APP_ID, $request); parent::__construct(Application::APP_ID, $request);
} }
public function index(string $url): JSONResponse { public function index(string $url): JSONResponse {
$podcast = $this->podcastDataReader->tryGetCachedPodcastData($url);
if ($podcast) {
return new JSONResponse($podcast);
}
$client = $this->clientService->newClient(); $client = $this->clientService->newClient();
$feed = $client->get($url); $feed = $client->get($url);
$podcast = PodcastData::parseRssXml((string) $feed->getBody()); $podcast = PodcastData::parseRssXml((string) $feed->getBody());
$this->podcastDataReader->trySetCachedPodcastData($url, $podcast);
return new JSONResponse($podcast->toArrayWithExtras(), $feed->getStatusCode()); return new JSONResponse($podcast, $feed->getStatusCode());
} }
} }

View File

@ -6,7 +6,6 @@ namespace OCA\RePod\Core\EpisodeAction;
use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionReader as CoreEpisodeActionReader; use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionReader as CoreEpisodeActionReader;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository; use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
use OCA\RePod\Core\PodcastData\PodcastData;
use OCA\RePod\Service\UserService; use OCA\RePod\Service\UserService;
class EpisodeActionReader extends CoreEpisodeActionReader class EpisodeActionReader extends CoreEpisodeActionReader
@ -53,40 +52,40 @@ class EpisodeActionReader extends CoreEpisodeActionReader
$name = (string) $item->title; $name = (string) $item->title;
// Get episode link // Get episode link
$link = PodcastData::stringOrNull($item->link); $link = $this->stringOrNull($item->link);
// Get episode image // Get episode image
$image = PodcastData::stringOrNull($item->image->url); $image = $this->stringOrNull($item->image->url);
if (!$image && $iTunesItemChildren) { if (!$image && $iTunesItemChildren) {
$imageAttributes = $iTunesItemChildren->image->attributes(); $imageAttributes = $iTunesItemChildren->image->attributes();
$image = PodcastData::stringOrNull($imageAttributes ? (string) $imageAttributes->href : ''); $image = $this->stringOrNull($imageAttributes ? (string) $imageAttributes->href : '');
} }
if (!$image) { if (!$image) {
$image = PodcastData::stringOrNull($channel->image->url); $image = $this->stringOrNull($channel->image->url);
} }
if (!$image && $iTunesChannelChildren) { if (!$image && $iTunesChannelChildren) {
$imageAttributes = $iTunesChannelChildren->image->attributes(); $imageAttributes = $iTunesChannelChildren->image->attributes();
$image = PodcastData::stringOrNull($imageAttributes ? (string) $imageAttributes->href : ''); $image = $this->stringOrNull($imageAttributes ? (string) $imageAttributes->href : '');
} }
if (!$image) { if (!$image) {
preg_match('/<itunes:image\s+href="([^"]+)"/', $xmlString, $matches); preg_match('/<itunes:image\s+href="([^"]+)"/', $xmlString, $matches);
$image = PodcastData::stringOrNull($matches[1]); $image = $this->stringOrNull($matches[1]);
} }
// Get episode description // Get episode description
$itemContent = $item->children('content', true); $itemContent = $item->children('content', true);
if ($itemContent) { if ($itemContent) {
$description = PodcastData::stringOrNull($itemContent->encoded); $description = $this->stringOrNull($itemContent->encoded);
} else { } else {
$description = PodcastData::stringOrNull($item->description); $description = $this->stringOrNull($item->description);
} }
if (!$description && $iTunesItemChildren) { if (!$description && $iTunesItemChildren) {
$description = PodcastData::stringOrNull($iTunesItemChildren->summary); $description = $this->stringOrNull($iTunesItemChildren->summary);
} }
// Remove tags // Remove tags
@ -94,9 +93,9 @@ class EpisodeActionReader extends CoreEpisodeActionReader
// Get episode duration // Get episode duration
if ($iTunesItemChildren) { if ($iTunesItemChildren) {
$rawDuration = PodcastData::stringOrNull($iTunesItemChildren->duration); $rawDuration = $this->stringOrNull($iTunesItemChildren->duration);
} else { } else {
$rawDuration = PodcastData::stringOrNull($item->duration); $rawDuration = $this->stringOrNull($item->duration);
} }
$splitDuration = array_reverse(explode(':', $rawDuration ?? '')); $splitDuration = array_reverse(explode(':', $rawDuration ?? ''));
@ -105,7 +104,7 @@ class EpisodeActionReader extends CoreEpisodeActionReader
$duration += !empty($splitDuration[2]) ? (int) $splitDuration[2] * 60 : 0; $duration += !empty($splitDuration[2]) ? (int) $splitDuration[2] * 60 : 0;
// Get episode pubDate // Get episode pubDate
$rawPubDate = PodcastData::stringOrNull($item->pubDate); $rawPubDate = $this->stringOrNull($item->pubDate);
$pubDate = $rawPubDate ? new \DateTime($rawPubDate) : null; $pubDate = $rawPubDate ? new \DateTime($rawPubDate) : null;
$episodes[] = new EpisodeActionExtraData( $episodes[] = new EpisodeActionExtraData(
@ -127,4 +126,15 @@ class EpisodeActionReader extends CoreEpisodeActionReader
return $episodes; return $episodes;
} }
/**
* @param null|\SimpleXMLElement|string $value
*/
private function stringOrNull($value): ?string {
if ($value) {
return (string) $value;
}
return null;
}
} }

View File

@ -1,105 +0,0 @@
<?php
declare(strict_types=1);
namespace OCA\RePod\Core\PodcastData;
use OCA\GPodderSync\Core\PodcastData\PodcastData as CorePodcastData;
/**
* @psalm-type PodcastDataType = array{
* title: ?string,
* author: ?string,
* link: ?string,
* description: ?string,
* imageUrl: ?string,
* fetchedAtUnix: int,
* imageBlob: ?string,
* atomLink: ?string
* }
*/
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;
}
/**
* @return PodcastDataType
*/
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;
}
}

View File

@ -26,7 +26,6 @@ import Episodes from '../components/Feed/Episodes.vue'
import Loading from '../components/Atoms/Loading.vue' import Loading from '../components/Atoms/Loading.vue'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import { toUrl } from '../utils/url.js'
export default { export default {
name: 'Feed', name: 'Feed',
@ -53,11 +52,6 @@ export default {
async mounted() { async mounted() {
try { try {
const podcastData = await axios.get(generateUrl('/apps/repod/podcast?url={url}', { url: this.url })) const podcastData = await axios.get(generateUrl('/apps/repod/podcast?url={url}', { url: this.url }))
if (podcastData.data.atomLink && podcastData.data.atomLink !== this.url) {
this.$router.push(toUrl(podcastData.data.atomLink))
}
this.feed = podcastData.data this.feed = podcastData.data
} catch (e) { } catch (e) {
this.failed = true this.failed = true