feat: add export opml
All checks were successful
repod / xml (push) Successful in 28s
repod / php (push) Successful in 1m0s
repod / nodejs (push) Successful in 1m58s
repod / release (push) Has been skipped

This commit is contained in:
Michel Roux 2024-01-18 13:28:06 +01:00
parent 2ed16a316e
commit c28abc7564
3 changed files with 109 additions and 20 deletions

View File

@ -15,6 +15,7 @@ return [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'], ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'episodes#action', 'url' => '/episodes/action', 'verb' => 'GET'], ['name' => 'episodes#action', 'url' => '/episodes/action', 'verb' => 'GET'],
['name' => 'episodes#list', 'url' => '/episodes/list', 'verb' => 'GET'], ['name' => 'episodes#list', 'url' => '/episodes/list', 'verb' => 'GET'],
['name' => 'opml#export', 'url' => '/opml/export', 'verb' => 'GET'],
['name' => 'podcast#index', 'url' => '/podcast', 'verb' => 'GET'], ['name' => 'podcast#index', 'url' => '/podcast', 'verb' => 'GET'],
['name' => 'search#index', 'url' => '/search', 'verb' => 'GET'], ['name' => 'search#index', 'url' => '/search', 'verb' => 'GET'],
['name' => 'tops#hot', 'url' => '/tops/hot', 'verb' => 'GET'], ['name' => 'tops#hot', 'url' => '/tops/hot', 'verb' => 'GET'],

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace OCA\RePod\Controller;
use OCA\GPodderSync\Core\PodcastData\PodcastDataReader;
use OCA\GPodderSync\Core\PodcastData\PodcastMetricsReader;
use OCA\RePod\AppInfo\Application;
use OCA\RePod\Service\UserService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\IL10N;
use OCP\IRequest;
class OpmlController extends Controller
{
public function __construct(
IRequest $request,
private IL10N $l10n,
private PodcastDataReader $podcastDataReader,
private PodcastMetricsReader $podcastMetricsReader,
private UserService $userService
) {
parent::__construct(Application::APP_ID, $request);
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function export(): DataDownloadResponse {
// https://github.com/AntennaPod/AntennaPod/blob/master/core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlWriter.java
$xml = new \SimpleXMLElement('<opml/>', namespaceOrPrefix: 'http://xmlpull.org/v1/doc/features.html#indent-output');
$xml->addAttribute('version', '2.0');
$dateCreated = new \DateTime();
$head = $xml->addChild('head');
if (isset($head)) {
$head->addChild('title', $this->l10n->t('RePod Subscriptions'));
$head->addChild('dateCreated', $dateCreated->format(\DateTime::RFC822));
}
$body = $xml->addChild('body');
if (isset($body)) {
$subscriptions = $this->podcastMetricsReader->metrics($this->userService->getUserUID());
foreach ($subscriptions as $subscription) {
$podcast = $this->podcastDataReader->getCachedOrFetchPodcastData($subscription->getUrl(), $this->userService->getUserUID());
if ($podcast) {
$outline = $body->addChild('outline');
if (isset($outline)) {
$title = $podcast->getTitle();
$link = $podcast->getLink();
if ($title) {
$outline->addAttribute('text', $title);
$outline->addAttribute('title', $title);
}
if ($link) {
$outline->addAttribute('xmlUrl', $link);
}
}
}
}
}
return new DataDownloadResponse((string) $xml->asXML(), 'repod-'.$dateCreated->getTimestamp().'.opml', ' application/xml');
}
}

View File

@ -1,33 +1,46 @@
<template> <template>
<NcAppNavigationSettings> <NcAppNavigationSettings>
<div class="setting"> <NcAppNavigationItem :name="t('repod', 'Playback speed')">
<label> <template #icon>
<SpeedometerSlow v-if="player.rate < 1" :size="20" /> <SpeedometerSlow v-if="player.rate < 1" :size="20" />
<SpeedometerMedium v-if="player.rate === 1" :size="20" /> <SpeedometerMedium v-if="player.rate === 1" :size="20" />
<Speedometer v-if="player.rate > 1" :size="20" /> <Speedometer v-if="player.rate > 1" :size="20" />
{{ t('repod', 'Playback speed') }} </template>
</label> <template #extra>
<div> <div class="extra">
<Minus class="pointer" :size="20" @click="changeRate(-.5)" /> <Minus class="pointer" :size="20" @click="changeRate(-.5)" />
<NcCounterBubble>x{{ player.rate }}</NcCounterBubble> <NcCounterBubble class="counter">
<Plus class="pointer" :size="20" @click="changeRate(.5)" /> x{{ player.rate }}
</div> </NcCounterBubble>
</div> <Plus class="pointer" :size="20" @click="changeRate(.5)" />
</div>
</template>
</NcAppNavigationItem>
<NcAppNavigationItem :href="generateUrl('/apps/repod/opml/export')"
:name="t('repod', 'Export subscriptions')">
<template #icon>
<Export :size="20" />
</template>
</NcAppNavigationItem>
</NcAppNavigationSettings> </NcAppNavigationSettings>
</template> </template>
<script> <script>
import { NcAppNavigationSettings, NcCounterBubble } from '@nextcloud/vue' import { NcAppNavigationItem, NcAppNavigationSettings, NcCounterBubble } from '@nextcloud/vue'
import Export from 'vue-material-design-icons/Export.vue'
import Minus from 'vue-material-design-icons/Minus.vue' import Minus from 'vue-material-design-icons/Minus.vue'
import Plus from 'vue-material-design-icons/Plus.vue' import Plus from 'vue-material-design-icons/Plus.vue'
import Speedometer from 'vue-material-design-icons/Speedometer.vue' import Speedometer from 'vue-material-design-icons/Speedometer.vue'
import SpeedometerMedium from 'vue-material-design-icons/SpeedometerMedium.vue' import SpeedometerMedium from 'vue-material-design-icons/SpeedometerMedium.vue'
import SpeedometerSlow from 'vue-material-design-icons/SpeedometerSlow.vue' import SpeedometerSlow from 'vue-material-design-icons/SpeedometerSlow.vue'
import { generateUrl } from '@nextcloud/router'
export default { export default {
name: 'Settings', name: 'Settings',
components: { components: {
Export,
Minus, Minus,
NcAppNavigationItem,
NcAppNavigationSettings, NcAppNavigationSettings,
NcCounterBubble, NcCounterBubble,
Plus, Plus,
@ -41,6 +54,7 @@ export default {
}, },
}, },
methods: { methods: {
generateUrl,
changeRate(diff) { changeRate(diff) {
if (this.player.rate + diff > 0) { if (this.player.rate + diff > 0) {
this.$store.dispatch('player/rate', this.player.rate + diff) this.$store.dispatch('player/rate', this.player.rate + diff)
@ -51,18 +65,17 @@ export default {
</script> </script>
<style scoped> <style scoped>
.pointer { .counter {
cursor: pointer; height: 20px;
} }
.setting { .extra {
display: flex; align-items: center;
justify-content: space-between;
}
.setting > label,
.setting > div {
display: flex; display: flex;
gap: .5rem; gap: .5rem;
} }
.pointer {
cursor: pointer;
}
</style> </style>