From 195a30096f93f06be513598ff73a32ee0b68598c Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 3 May 2022 18:52:34 -0500 Subject: [PATCH] Update:Experimental RSS feed setting custom slugs with default to library item id #553 --- .../components/modals/rssfeed/ViewModal.vue | 69 +++++++++++++++++-- client/pages/item/_id/index.vue | 28 +------- client/plugins/init.client.js | 25 +++++++ server/controllers/PodcastController.js | 6 ++ server/managers/RssFeedManager.js | 25 ++++--- 5 files changed, 112 insertions(+), 41 deletions(-) diff --git a/client/components/modals/rssfeed/ViewModal.vue b/client/components/modals/rssfeed/ViewModal.vue index 82a7193a..cc8240ac 100644 --- a/client/components/modals/rssfeed/ViewModal.vue +++ b/client/components/modals/rssfeed/ViewModal.vue @@ -6,18 +6,27 @@
-
+

Podcast RSS Feed is Open

- + - content_copy + content_copy +
+
+
+

Open RSS Feed

+ +
+ +

Feed will be {{ demoFeedUrl }}

- Close RSS Feed + Close RSS Feed + Open RSS Feed
@@ -35,7 +44,9 @@ export default { }, data() { return { - processing: false + processing: false, + newFeedSlug: null, + currentFeedUrl: null } }, watch: { @@ -57,6 +68,9 @@ export default { this.$emit('input', val) } }, + libraryItemId() { + return this.libraryItem.id + }, media() { return this.libraryItem.media || {} }, @@ -68,9 +82,48 @@ export default { }, userIsAdminOrUp() { return this.$store.getters['user/getIsAdminOrUp'] + }, + demoFeedUrl() { + return `${window.origin}/feed/${this.newFeedSlug}` } }, methods: { + openFeed() { + if (!this.newFeedSlug) { + this.$toast.error('Must set a feed slug') + return + } + + var sanitized = this.$sanitizeSlug(this.newFeedSlug) + if (this.newFeedSlug !== sanitized) { + this.newFeedSlug = sanitized + this.$toast.warning('Slug had to be modified - Run again') + return + } + + const payload = { + serverAddress: window.origin, + slug: this.newFeedSlug + } + if (this.$isDev) payload.serverAddress = 'http://localhost:3333' + + console.log('Payload', payload) + this.$axios + .$post(`/api/podcasts/${this.libraryItemId}/open-feed`, payload) + .then((data) => { + if (data.success) { + console.log('Opened RSS Feed', data) + this.currentFeedUrl = data.feedUrl + } else { + const errorMsg = data.error || 'Unknown error' + this.$toast.error(errorMsg) + } + }) + .catch((error) => { + console.error('Failed to open RSS Feed', error) + this.$toast.error() + }) + }, copyToClipboard(str) { this.$copyToClipboard(str, this) }, @@ -89,7 +142,11 @@ export default { this.$toast.error() }) }, - init() {} + init() { + if (!this.libraryItem) return + this.newFeedSlug = this.libraryItem.id + this.currentFeedUrl = this.feedUrl + } }, mounted() {} } diff --git a/client/pages/item/_id/index.vue b/client/pages/item/_id/index.vue index dbca379e..5361be48 100644 --- a/client/pages/item/_id/index.vue +++ b/client/pages/item/_id/index.vue @@ -492,33 +492,7 @@ export default { this.$store.commit('globals/setShowUserCollectionsModal', true) }, clickRSSFeed() { - if (!this.rssFeedUrl) { - if (confirm(`Are you sure you want to open an RSS Feed for this podcast?`)) { - this.openRSSFeed() - } - } else { - this.showRssFeedModal = true - } - }, - openRSSFeed() { - const payload = { - serverAddress: window.origin - } - if (this.$isDev) payload.serverAddress = 'http://localhost:3333' - - console.log('Payload', payload) - this.$axios - .$post(`/api/podcasts/${this.libraryItemId}/open-feed`, payload) - .then((data) => { - if (data.success) { - console.log('Opened RSS Feed', data) - this.rssFeedUrl = data.feedUrl - this.showRssFeedModal = true - } - }) - .catch((error) => { - console.error('Failed to open RSS Feed', error) - }) + this.showRssFeedModal = true }, episodeDownloadQueued(episodeDownload) { if (episodeDownload.libraryItemId === this.libraryItemId) { diff --git a/client/plugins/init.client.js b/client/plugins/init.client.js index fe640a13..71585580 100644 --- a/client/plugins/init.client.js +++ b/client/plugins/init.client.js @@ -125,6 +125,31 @@ Vue.prototype.$sanitizeFilename = (input, replacement = '') => { return sanitized } +// SOURCE: https://gist.github.com/spyesx/561b1d65d4afb595f295 +// modified: allowed underscores +Vue.prototype.$sanitizeSlug = (str) => { + if (!str) return '' + + str = str.replace(/^\s+|\s+$/g, '') // trim + str = str.toLowerCase() + + // remove accents, swap ñ for n, etc + var from = "àáäâèéëêìíïîòóöôùúüûñçěščřžýúůďťň·/,:;" + var to = "aaaaeeeeiiiioooouuuuncescrzyuudtn-----" + + for (var i = 0, l = from.length; i < l; i++) { + str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i)) + } + + str = str.replace('.', '-') // replace a dot by a dash + .replace(/[^a-z0-9 -_]/g, '') // remove invalid chars + .replace(/\s+/g, '-') // collapse whitespace and replace by a dash + .replace(/-+/g, '-') // collapse dashes + .replace(/\//g, '') // collapse all forward-slashes + + return str +} + Vue.prototype.$copyToClipboard = (str, ctx) => { return new Promise((resolve) => { if (!navigator.clipboard) { diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index ea22ce0e..4c54a262 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -173,6 +173,12 @@ class PodcastController { } const feedData = this.rssFeedManager.openPodcastFeed(req.user, req.libraryItem, req.body) + if (feedData.error) { + return res.json({ + success: false, + error: feedData.error + }) + } res.json({ success: true, diff --git a/server/managers/RssFeedManager.js b/server/managers/RssFeedManager.js index 6250a5e5..454e8a75 100644 --- a/server/managers/RssFeedManager.js +++ b/server/managers/RssFeedManager.js @@ -59,23 +59,23 @@ class RssFeedManager { readStream.pipe(res) } - openFeed(userId, feedId, libraryItem, serverAddress) { + openFeed(userId, slug, libraryItem, serverAddress) { const podcast = libraryItem.media - const feedUrl = `${serverAddress}/feed/${feedId}` + const feedUrl = `${serverAddress}/feed/${slug}` // Removed Podcast npm package and ip package const feed = new Podcast({ title: podcast.metadata.title, description: podcast.metadata.description, feedUrl, siteUrl: serverAddress, - imageUrl: podcast.coverPath ? `${serverAddress}/feed/${feedId}/cover` : `${serverAddress}/Logo.png`, + imageUrl: podcast.coverPath ? `${serverAddress}/feed/${slug}/cover` : `${serverAddress}/Logo.png`, author: podcast.metadata.author || 'advplyr', language: 'en' }) podcast.episodes.forEach((episode) => { var contentUrl = episode.audioTrack.contentUrl.replace(/\\/g, '/') - contentUrl = contentUrl.replace(`/s/item/${libraryItem.id}`, `/feed/${feedId}/item`) + contentUrl = contentUrl.replace(`/s/item/${libraryItem.id}`, `/feed/${slug}/item`) feed.addItem({ title: episode.title, @@ -92,7 +92,8 @@ class RssFeedManager { }) const feedData = { - id: feedId, + id: slug, + slug, userId, libraryItemId: libraryItem.id, libraryItemPath: libraryItem.path, @@ -101,14 +102,22 @@ class RssFeedManager { feedUrl, feed } - this.feeds[feedId] = feedData + this.feeds[slug] = feedData return feedData } openPodcastFeed(user, libraryItem, options) { const serverAddress = options.serverAddress - const feedId = getId('feed') - const feedData = this.openFeed(user.id, feedId, libraryItem, serverAddress) + const slug = options.slug + + if (this.feeds[slug]) { + Logger.error(`[RssFeedManager] Slug already in use`) + return { + error: `Slug "${slug}" already in use` + } + } + + const feedData = this.openFeed(user.id, slug, libraryItem, serverAddress) Logger.debug(`[RssFeedManager] Opened podcast feed ${feedData.feedUrl}`) this.emitter('rss_feed_open', { libraryItemId: libraryItem.id, feedUrl: feedData.feedUrl }) return feedData