feat: import subscriptions
This commit is contained in:
parent
c28abc7564
commit
751c3c1e01
@ -16,6 +16,7 @@ return [
|
|||||||
['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' => 'opml#export', 'url' => '/opml/export', 'verb' => 'GET'],
|
||||||
|
['name' => 'opml#import', 'url' => '/opml/import', 'verb' => 'POST'],
|
||||||
['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'],
|
||||||
|
@ -30,9 +30,7 @@ class EpisodesController extends Controller
|
|||||||
public function list(string $url): JSONResponse {
|
public function list(string $url): JSONResponse {
|
||||||
$client = $this->clientService->newClient();
|
$client = $this->clientService->newClient();
|
||||||
$feed = $client->get($url);
|
$feed = $client->get($url);
|
||||||
|
|
||||||
$episodes = $this->episodeActionReader->parseRssXml((string) $feed->getBody());
|
$episodes = $this->episodeActionReader->parseRssXml((string) $feed->getBody());
|
||||||
|
|
||||||
usort($episodes, fn (EpisodeActionExtraData $a, EpisodeActionExtraData $b) => $b->getPubDate() <=> $a->getPubDate());
|
usort($episodes, fn (EpisodeActionExtraData $a, EpisodeActionExtraData $b) => $b->getPubDate() <=> $a->getPubDate());
|
||||||
$episodes = array_values(array_intersect_key($episodes, array_unique(array_map(fn (EpisodeActionExtraData $episode) => $episode->getGuid(), $episodes))));
|
$episodes = array_values(array_intersect_key($episodes, array_unique(array_map(fn (EpisodeActionExtraData $episode) => $episode->getGuid(), $episodes))));
|
||||||
|
|
||||||
|
@ -6,10 +6,12 @@ namespace OCA\RePod\Controller;
|
|||||||
|
|
||||||
use OCA\GPodderSync\Core\PodcastData\PodcastDataReader;
|
use OCA\GPodderSync\Core\PodcastData\PodcastDataReader;
|
||||||
use OCA\GPodderSync\Core\PodcastData\PodcastMetricsReader;
|
use OCA\GPodderSync\Core\PodcastData\PodcastMetricsReader;
|
||||||
|
use OCA\GPodderSync\Core\SubscriptionChange\SubscriptionChangeSaver;
|
||||||
use OCA\RePod\AppInfo\Application;
|
use OCA\RePod\AppInfo\Application;
|
||||||
use OCA\RePod\Service\UserService;
|
use OCA\RePod\Service\UserService;
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Http\DataDownloadResponse;
|
use OCP\AppFramework\Http\DataDownloadResponse;
|
||||||
|
use OCP\AppFramework\Http\Response;
|
||||||
use OCP\IL10N;
|
use OCP\IL10N;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
|
||||||
@ -20,6 +22,7 @@ class OpmlController extends Controller
|
|||||||
private IL10N $l10n,
|
private IL10N $l10n,
|
||||||
private PodcastDataReader $podcastDataReader,
|
private PodcastDataReader $podcastDataReader,
|
||||||
private PodcastMetricsReader $podcastMetricsReader,
|
private PodcastMetricsReader $podcastMetricsReader,
|
||||||
|
private SubscriptionChangeSaver $subscriptionChangeSaver,
|
||||||
private UserService $userService
|
private UserService $userService
|
||||||
) {
|
) {
|
||||||
parent::__construct(Application::APP_ID, $request);
|
parent::__construct(Application::APP_ID, $request);
|
||||||
@ -54,6 +57,8 @@ class OpmlController extends Controller
|
|||||||
$outline = $body->addChild('outline');
|
$outline = $body->addChild('outline');
|
||||||
|
|
||||||
if (isset($outline)) {
|
if (isset($outline)) {
|
||||||
|
$outline->addAttribute('xmlUrl', $subscription->getUrl());
|
||||||
|
|
||||||
$title = $podcast->getTitle();
|
$title = $podcast->getTitle();
|
||||||
$link = $podcast->getLink();
|
$link = $podcast->getLink();
|
||||||
|
|
||||||
@ -63,7 +68,7 @@ class OpmlController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($link) {
|
if ($link) {
|
||||||
$outline->addAttribute('xmlUrl', $link);
|
$outline->addAttribute('htmlUrl', $link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,4 +77,28 @@ class OpmlController extends Controller
|
|||||||
|
|
||||||
return new DataDownloadResponse((string) $xml->asXML(), 'repod-'.$dateCreated->getTimestamp().'.opml', ' application/xml');
|
return new DataDownloadResponse((string) $xml->asXML(), 'repod-'.$dateCreated->getTimestamp().'.opml', ' application/xml');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*/
|
||||||
|
public function import(): Response {
|
||||||
|
$file = $this->request->getUploadedFile('import');
|
||||||
|
|
||||||
|
if ($file) {
|
||||||
|
$xml = new \SimpleXMLElement(file_get_contents((string) $file['tmp_name']));
|
||||||
|
|
||||||
|
/** @var \SimpleXMLElement[] $outlines */
|
||||||
|
$outlines = $xml->body->children();
|
||||||
|
|
||||||
|
$toSubscribe = [];
|
||||||
|
foreach ($outlines as $outline) {
|
||||||
|
$toSubscribe[] = (string) $outline['xmlUrl'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->subscriptionChangeSaver->saveSubscriptionChanges($toSubscribe, [], $this->userService->getUserUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ class FyydService implements IPodProvider
|
|||||||
$langClient = $this->clientService->newClient();
|
$langClient = $this->clientService->newClient();
|
||||||
$langResponse = $langClient->get(self::BASE_URL.'feature/podcast/hot/languages');
|
$langResponse = $langClient->get(self::BASE_URL.'feature/podcast/hot/languages');
|
||||||
$langJson = (array) json_decode((string) $langResponse->getBody(), true, flags: JSON_THROW_ON_ERROR);
|
$langJson = (array) json_decode((string) $langResponse->getBody(), true, flags: JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
if (array_key_exists('data', $langJson) && is_array($langJson['data'])) {
|
if (array_key_exists('data', $langJson) && is_array($langJson['data'])) {
|
||||||
$language = in_array($userLang, $langJson['data']) ? $userLang : 'en';
|
$language = in_array($userLang, $langJson['data']) ? $userLang : 'en';
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NcAppNavigationItem>
|
</NcAppNavigationItem>
|
||||||
|
<NcAppNavigationItem :name="t('repod', 'Import subscriptions')" @click="importModal = true">
|
||||||
|
<template #icon>
|
||||||
|
<Import :size="20" />
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<NcModal v-if="importModal" @close="importModal = false">
|
||||||
|
<div class="importModal">
|
||||||
|
<h2>{{ t('repod', 'Import OPML file') }}</h2>
|
||||||
|
<form v-if="!importLoading"
|
||||||
|
id="import"
|
||||||
|
:action="generateUrl('/apps/repod/opml/import')"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
method="post"
|
||||||
|
@submit.prevent="importOpml">
|
||||||
|
<input accept="application/xml,.opml"
|
||||||
|
name="import"
|
||||||
|
required
|
||||||
|
type="file">
|
||||||
|
<input type="submit">
|
||||||
|
</form>
|
||||||
|
<Loading v-if="importLoading" />
|
||||||
|
</div>
|
||||||
|
</NcModal>
|
||||||
|
</template>
|
||||||
|
</NcAppNavigationItem>
|
||||||
<NcAppNavigationItem :href="generateUrl('/apps/repod/opml/export')"
|
<NcAppNavigationItem :href="generateUrl('/apps/repod/opml/export')"
|
||||||
:name="t('repod', 'Export subscriptions')">
|
:name="t('repod', 'Export subscriptions')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
@ -26,28 +51,40 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { NcAppNavigationItem, NcAppNavigationSettings, NcCounterBubble } from '@nextcloud/vue'
|
import { NcAppNavigationItem, NcAppNavigationSettings, NcCounterBubble, NcModal } from '@nextcloud/vue'
|
||||||
import Export from 'vue-material-design-icons/Export.vue'
|
import Export from 'vue-material-design-icons/Export.vue'
|
||||||
|
import Import from 'vue-material-design-icons/Import.vue'
|
||||||
|
import Loading from '../Atoms/Loading.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 axios from '@nextcloud/axios'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
components: {
|
components: {
|
||||||
Export,
|
Export,
|
||||||
|
Import,
|
||||||
|
Loading,
|
||||||
Minus,
|
Minus,
|
||||||
NcAppNavigationItem,
|
NcAppNavigationItem,
|
||||||
NcAppNavigationSettings,
|
NcAppNavigationSettings,
|
||||||
NcCounterBubble,
|
NcCounterBubble,
|
||||||
|
NcModal,
|
||||||
Plus,
|
Plus,
|
||||||
Speedometer,
|
Speedometer,
|
||||||
SpeedometerMedium,
|
SpeedometerMedium,
|
||||||
SpeedometerSlow,
|
SpeedometerSlow,
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
importModal: false,
|
||||||
|
importLoading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
player() {
|
player() {
|
||||||
return this.$store.state.player
|
return this.$store.state.player
|
||||||
@ -60,6 +97,17 @@ export default {
|
|||||||
this.$store.dispatch('player/rate', this.player.rate + diff)
|
this.$store.dispatch('player/rate', this.player.rate + diff)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async importOpml(event) {
|
||||||
|
try {
|
||||||
|
const formData = new FormData(event.target)
|
||||||
|
this.importLoading = true
|
||||||
|
await axios.post(event.target.action, formData)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -75,6 +123,13 @@ export default {
|
|||||||
gap: .5rem;
|
gap: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.importModal {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.pointer {
|
.pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user