refacto: remove atomlink that leads to weird bugs
This commit is contained in:
parent
c48432c0b5
commit
540c5df7e5
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user