mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:OPML Export #1260
This commit is contained in:
		
							parent
							
								
									019063e6f4
								
							
						
					
					
						commit
						15aaf2863c
					
				@ -81,6 +81,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        <!-- issues page remove all button -->
 | 
					        <!-- issues page remove all button -->
 | 
				
			||||||
        <ui-btn v-if="isIssuesFilter && userCanDelete && !isBatchSelecting" :loading="processingIssues" color="error" small class="ml-4" @click="removeAllIssues">{{ $strings.ButtonRemoveAll }} {{ numShowing }} {{ entityName }}</ui-btn>
 | 
					        <ui-btn v-if="isIssuesFilter && userCanDelete && !isBatchSelecting" :loading="processingIssues" color="error" small class="ml-4" @click="removeAllIssues">{{ $strings.ButtonRemoveAll }} {{ numShowing }} {{ entityName }}</ui-btn>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" menu-width="110px" class="ml-2" @action="contextMenuAction" />
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
      <!-- search page -->
 | 
					      <!-- search page -->
 | 
				
			||||||
      <template v-else-if="page === 'search'">
 | 
					      <template v-else-if="page === 'search'">
 | 
				
			||||||
@ -186,6 +188,9 @@ export default {
 | 
				
			|||||||
    userCanUpdate() {
 | 
					    userCanUpdate() {
 | 
				
			||||||
      return this.$store.getters['user/getUserCanUpdate']
 | 
					      return this.$store.getters['user/getUserCanUpdate']
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDownload() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDownload']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    currentLibraryId() {
 | 
					    currentLibraryId() {
 | 
				
			||||||
      return this.$store.state.libraries.currentLibraryId
 | 
					      return this.$store.state.libraries.currentLibraryId
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -276,9 +281,29 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    isIssuesFilter() {
 | 
					    isIssuesFilter() {
 | 
				
			||||||
      return this.filterBy === 'issues' && this.$route.query.filter === 'issues'
 | 
					      return this.filterBy === 'issues' && this.$route.query.filter === 'issues'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    contextMenuItems() {
 | 
				
			||||||
 | 
					      const items = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.isPodcastLibrary && this.isLibraryPage && this.userCanDownload) {
 | 
				
			||||||
 | 
					        items.push({
 | 
				
			||||||
 | 
					          text: 'Export OPML',
 | 
				
			||||||
 | 
					          action: 'export-opml'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return items
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
 | 
					    contextMenuAction(action) {
 | 
				
			||||||
 | 
					      if (action === 'export-opml') {
 | 
				
			||||||
 | 
					        this.exportOPML()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    exportOPML() {
 | 
				
			||||||
 | 
					      this.$downloadFile(`/api/libraries/${this.currentLibraryId}/opml?token=${this.$store.getters['user/getToken']}`, null, true)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    seriesContextMenuAction(action) {
 | 
					    seriesContextMenuAction(action) {
 | 
				
			||||||
      if (action === 'open-rss-feed') {
 | 
					      if (action === 'open-rss-feed') {
 | 
				
			||||||
        this.showOpenSeriesRSSFeed()
 | 
					        this.showOpenSeriesRSSFeed()
 | 
				
			||||||
 | 
				
			|||||||
@ -107,15 +107,7 @@ export default {
 | 
				
			|||||||
      this.$store.commit('globals/setConfirmPrompt', payload)
 | 
					      this.$store.commit('globals/setConfirmPrompt', payload)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    downloadLibraryFile() {
 | 
					    downloadLibraryFile() {
 | 
				
			||||||
      const a = document.createElement('a')
 | 
					      this.$downloadFile(this.downloadUrl, this.track.metadata.filename)
 | 
				
			||||||
      a.style.display = 'none'
 | 
					 | 
				
			||||||
      a.href = this.downloadUrl
 | 
					 | 
				
			||||||
      a.download = this.track.metadata.filename
 | 
					 | 
				
			||||||
      document.body.appendChild(a)
 | 
					 | 
				
			||||||
      a.click()
 | 
					 | 
				
			||||||
      setTimeout(() => {
 | 
					 | 
				
			||||||
        a.remove()
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  mounted() {}
 | 
					  mounted() {}
 | 
				
			||||||
 | 
				
			|||||||
@ -102,15 +102,7 @@ export default {
 | 
				
			|||||||
      this.$store.commit('globals/setConfirmPrompt', payload)
 | 
					      this.$store.commit('globals/setConfirmPrompt', payload)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    downloadLibraryFile() {
 | 
					    downloadLibraryFile() {
 | 
				
			||||||
      const a = document.createElement('a')
 | 
					      this.$downloadFile(this.downloadUrl, this.file.metadata.filename)
 | 
				
			||||||
      a.style.display = 'none'
 | 
					 | 
				
			||||||
      a.href = this.downloadUrl
 | 
					 | 
				
			||||||
      a.download = this.file.metadata.filename
 | 
					 | 
				
			||||||
      document.body.appendChild(a)
 | 
					 | 
				
			||||||
      a.click()
 | 
					 | 
				
			||||||
      setTimeout(() => {
 | 
					 | 
				
			||||||
        a.remove()
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  mounted() {}
 | 
					  mounted() {}
 | 
				
			||||||
 | 
				
			|||||||
@ -677,14 +677,7 @@ export default {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    downloadLibraryItem() {
 | 
					    downloadLibraryItem() {
 | 
				
			||||||
      const a = document.createElement('a')
 | 
					      this.$downloadFile(this.downloadUrl)
 | 
				
			||||||
      a.style.display = 'none'
 | 
					 | 
				
			||||||
      a.href = this.downloadUrl
 | 
					 | 
				
			||||||
      document.body.appendChild(a)
 | 
					 | 
				
			||||||
      a.click()
 | 
					 | 
				
			||||||
      setTimeout(() => {
 | 
					 | 
				
			||||||
        a.remove()
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    deleteLibraryItem() {
 | 
					    deleteLibraryItem() {
 | 
				
			||||||
      const payload = {
 | 
					      const payload = {
 | 
				
			||||||
 | 
				
			|||||||
@ -145,6 +145,25 @@ Vue.prototype.$getNextScheduledDate = (expression) => {
 | 
				
			|||||||
  return interval.next().toDate()
 | 
					  return interval.next().toDate()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vue.prototype.$downloadFile = (url, filename = null, openInNewTab = false) => {
 | 
				
			||||||
 | 
					  const a = document.createElement('a')
 | 
				
			||||||
 | 
					  a.style.display = 'none'
 | 
				
			||||||
 | 
					  a.href = url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (filename) {
 | 
				
			||||||
 | 
					    a.download = filename
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (openInNewTab) {
 | 
				
			||||||
 | 
					    a.target = '_blank'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  document.body.appendChild(a)
 | 
				
			||||||
 | 
					  a.click()
 | 
				
			||||||
 | 
					  setTimeout(() => {
 | 
				
			||||||
 | 
					    a.remove()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function supplant(str, subs) {
 | 
					export function supplant(str, subs) {
 | 
				
			||||||
  // source: http://crockford.com/javascript/remedial.html
 | 
					  // source: http://crockford.com/javascript/remedial.html
 | 
				
			||||||
  return str.replace(/{([^{}]*)}/g,
 | 
					  return str.replace(/{([^{}]*)}/g,
 | 
				
			||||||
 | 
				
			|||||||
@ -848,6 +848,12 @@ class LibraryController {
 | 
				
			|||||||
    res.json(payload)
 | 
					    res.json(payload)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getOPMLFile(req, res) {
 | 
				
			||||||
 | 
					    const opmlText = this.podcastManager.generateOPMLFileText(req.libraryItems)
 | 
				
			||||||
 | 
					    res.type('application/xml')
 | 
				
			||||||
 | 
					    res.send(opmlText)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  middleware(req, res, next) {
 | 
					  middleware(req, res, next) {
 | 
				
			||||||
    if (!req.user.checkCanAccessLibrary(req.params.id)) {
 | 
					    if (!req.user.checkCanAccessLibrary(req.params.id)) {
 | 
				
			||||||
      Logger.warn(`[LibraryController] Library ${req.params.id} not accessible to user ${req.user.username}`)
 | 
					      Logger.warn(`[LibraryController] Library ${req.params.id} not accessible to user ${req.user.username}`)
 | 
				
			||||||
 | 
				
			|||||||
@ -109,7 +109,7 @@ class PodcastController {
 | 
				
			|||||||
    res.json({ podcast })
 | 
					    res.json({ podcast })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getOPMLFeeds(req, res) {
 | 
					  async getFeedsFromOPMLText(req, res) {
 | 
				
			||||||
    if (!req.body.opmlText) {
 | 
					    if (!req.body.opmlText) {
 | 
				
			||||||
      return res.sendStatus(400)
 | 
					      return res.sendStatus(400)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@ const { removeFile, downloadFile } = require('../utils/fileUtils')
 | 
				
			|||||||
const filePerms = require('../utils/filePerms')
 | 
					const filePerms = require('../utils/filePerms')
 | 
				
			||||||
const { levenshteinDistance } = require('../utils/index')
 | 
					const { levenshteinDistance } = require('../utils/index')
 | 
				
			||||||
const opmlParser = require('../utils/parsers/parseOPML')
 | 
					const opmlParser = require('../utils/parsers/parseOPML')
 | 
				
			||||||
 | 
					const opmlGenerator = require('../utils/generators/opmlGenerator')
 | 
				
			||||||
const prober = require('../utils/prober')
 | 
					const prober = require('../utils/prober')
 | 
				
			||||||
const ffmpegHelpers = require('../utils/ffmpegHelpers')
 | 
					const ffmpegHelpers = require('../utils/ffmpegHelpers')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -373,6 +374,10 @@ class PodcastManager {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  generateOPMLFileText(libraryItems) {
 | 
				
			||||||
 | 
					    return opmlGenerator.generate(libraryItems)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getDownloadQueueDetails(libraryId = null) {
 | 
					  getDownloadQueueDetails(libraryId = null) {
 | 
				
			||||||
    let _currentDownload = this.currentDownload
 | 
					    let _currentDownload = this.currentDownload
 | 
				
			||||||
    if (libraryId && _currentDownload?.libraryId !== libraryId) _currentDownload = null
 | 
					    if (libraryId && _currentDownload?.libraryId !== libraryId) _currentDownload = null
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ const fs = require('../libs/fsExtra')
 | 
				
			|||||||
const Path = require('path')
 | 
					const Path = require('path')
 | 
				
			||||||
const { version } = require('../../package.json')
 | 
					const { version } = require('../../package.json')
 | 
				
			||||||
const Logger = require('../Logger')
 | 
					const Logger = require('../Logger')
 | 
				
			||||||
const abmetadataGenerator = require('../utils/abmetadataGenerator')
 | 
					const abmetadataGenerator = require('../utils/generators/abmetadataGenerator')
 | 
				
			||||||
const LibraryFile = require('./files/LibraryFile')
 | 
					const LibraryFile = require('./files/LibraryFile')
 | 
				
			||||||
const Book = require('./mediaTypes/Book')
 | 
					const Book = require('./mediaTypes/Book')
 | 
				
			||||||
const Podcast = require('./mediaTypes/Podcast')
 | 
					const Podcast = require('./mediaTypes/Podcast')
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ const Ffmpeg = require('../libs/fluentFfmpeg')
 | 
				
			|||||||
const { secondsToTimestamp } = require('../utils/index')
 | 
					const { secondsToTimestamp } = require('../utils/index')
 | 
				
			||||||
const { writeConcatFile } = require('../utils/ffmpegHelpers')
 | 
					const { writeConcatFile } = require('../utils/ffmpegHelpers')
 | 
				
			||||||
const { AudioMimeType } = require('../utils/constants')
 | 
					const { AudioMimeType } = require('../utils/constants')
 | 
				
			||||||
const hlsPlaylistGenerator = require('../utils/hlsPlaylistGenerator')
 | 
					const hlsPlaylistGenerator = require('../utils/generators/hlsPlaylistGenerator')
 | 
				
			||||||
const AudioTrack = require('./files/AudioTrack')
 | 
					const AudioTrack = require('./files/AudioTrack')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Stream extends EventEmitter {
 | 
					class Stream extends EventEmitter {
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@ const BookMetadata = require('../metadata/BookMetadata')
 | 
				
			|||||||
const { areEquivalent, copyValue, cleanStringForSearch } = require('../../utils/index')
 | 
					const { areEquivalent, copyValue, cleanStringForSearch } = require('../../utils/index')
 | 
				
			||||||
const { parseOpfMetadataXML } = require('../../utils/parsers/parseOpfMetadata')
 | 
					const { parseOpfMetadataXML } = require('../../utils/parsers/parseOpfMetadata')
 | 
				
			||||||
const { parseOverdriveMediaMarkersAsChapters } = require('../../utils/parsers/parseOverdriveMediaMarkers')
 | 
					const { parseOverdriveMediaMarkersAsChapters } = require('../../utils/parsers/parseOverdriveMediaMarkers')
 | 
				
			||||||
const abmetadataGenerator = require('../../utils/abmetadataGenerator')
 | 
					const abmetadataGenerator = require('../../utils/generators/abmetadataGenerator')
 | 
				
			||||||
const { readTextFile, filePathToPOSIX } = require('../../utils/fileUtils')
 | 
					const { readTextFile, filePathToPOSIX } = require('../../utils/fileUtils')
 | 
				
			||||||
const AudioFile = require('../files/AudioFile')
 | 
					const AudioFile = require('../files/AudioFile')
 | 
				
			||||||
const AudioTrack = require('../files/AudioTrack')
 | 
					const AudioTrack = require('../files/AudioTrack')
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ const Logger = require('../../Logger')
 | 
				
			|||||||
const PodcastEpisode = require('../entities/PodcastEpisode')
 | 
					const PodcastEpisode = require('../entities/PodcastEpisode')
 | 
				
			||||||
const PodcastMetadata = require('../metadata/PodcastMetadata')
 | 
					const PodcastMetadata = require('../metadata/PodcastMetadata')
 | 
				
			||||||
const { areEquivalent, copyValue, cleanStringForSearch } = require('../../utils/index')
 | 
					const { areEquivalent, copyValue, cleanStringForSearch } = require('../../utils/index')
 | 
				
			||||||
const abmetadataGenerator = require('../../utils/abmetadataGenerator')
 | 
					const abmetadataGenerator = require('../../utils/generators/abmetadataGenerator')
 | 
				
			||||||
const { readTextFile, filePathToPOSIX } = require('../../utils/fileUtils')
 | 
					const { readTextFile, filePathToPOSIX } = require('../../utils/fileUtils')
 | 
				
			||||||
const { createNewSortInstance } = require('../../libs/fastSort')
 | 
					const { createNewSortInstance } = require('../../libs/fastSort')
 | 
				
			||||||
const naturalSort = createNewSortInstance({
 | 
					const naturalSort = createNewSortInstance({
 | 
				
			||||||
 | 
				
			|||||||
@ -90,7 +90,7 @@ class ApiRouter {
 | 
				
			|||||||
    this.router.get('/libraries/:id/matchall', LibraryController.middleware.bind(this), LibraryController.matchAll.bind(this))
 | 
					    this.router.get('/libraries/:id/matchall', LibraryController.middleware.bind(this), LibraryController.matchAll.bind(this))
 | 
				
			||||||
    this.router.post('/libraries/:id/scan', LibraryController.middleware.bind(this), LibraryController.scan.bind(this))
 | 
					    this.router.post('/libraries/:id/scan', LibraryController.middleware.bind(this), LibraryController.scan.bind(this))
 | 
				
			||||||
    this.router.get('/libraries/:id/recent-episodes', LibraryController.middleware.bind(this), LibraryController.getRecentEpisodes.bind(this))
 | 
					    this.router.get('/libraries/:id/recent-episodes', LibraryController.middleware.bind(this), LibraryController.getRecentEpisodes.bind(this))
 | 
				
			||||||
 | 
					    this.router.get('/libraries/:id/opml', LibraryController.middleware.bind(this), LibraryController.getOPMLFile.bind(this))
 | 
				
			||||||
    this.router.post('/libraries/order', LibraryController.reorder.bind(this))
 | 
					    this.router.post('/libraries/order', LibraryController.reorder.bind(this))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //
 | 
					    //
 | 
				
			||||||
@ -236,7 +236,7 @@ class ApiRouter {
 | 
				
			|||||||
    //
 | 
					    //
 | 
				
			||||||
    this.router.post('/podcasts', PodcastController.create.bind(this))
 | 
					    this.router.post('/podcasts', PodcastController.create.bind(this))
 | 
				
			||||||
    this.router.post('/podcasts/feed', PodcastController.getPodcastFeed.bind(this))
 | 
					    this.router.post('/podcasts/feed', PodcastController.getPodcastFeed.bind(this))
 | 
				
			||||||
    this.router.post('/podcasts/opml', PodcastController.getOPMLFeeds.bind(this))
 | 
					    this.router.post('/podcasts/opml', PodcastController.getFeedsFromOPMLText.bind(this))
 | 
				
			||||||
    this.router.get('/podcasts/:id/checknew', PodcastController.middleware.bind(this), PodcastController.checkNewEpisodes.bind(this))
 | 
					    this.router.get('/podcasts/:id/checknew', PodcastController.middleware.bind(this), PodcastController.checkNewEpisodes.bind(this))
 | 
				
			||||||
    this.router.get('/podcasts/:id/downloads', PodcastController.middleware.bind(this), PodcastController.getEpisodeDownloads.bind(this))
 | 
					    this.router.get('/podcasts/:id/downloads', PodcastController.middleware.bind(this), PodcastController.getEpisodeDownloads.bind(this))
 | 
				
			||||||
    this.router.get('/podcasts/:id/clear-queue', PodcastController.middleware.bind(this), PodcastController.clearEpisodeDownloadQueue.bind(this))
 | 
					    this.router.get('/podcasts/:id/clear-queue', PodcastController.middleware.bind(this), PodcastController.clearEpisodeDownloadQueue.bind(this))
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
const fs = require('../libs/fsExtra')
 | 
					const fs = require('../../libs/fsExtra')
 | 
				
			||||||
const filePerms = require('./filePerms')
 | 
					const filePerms = require('../filePerms')
 | 
				
			||||||
const package = require('../../package.json')
 | 
					const package = require('../../../package.json')
 | 
				
			||||||
const Logger = require('../Logger')
 | 
					const Logger = require('../../Logger')
 | 
				
			||||||
const { getId } = require('./index')
 | 
					const { getId } = require('../index')
 | 
				
			||||||
const areEquivalent = require('../utils/areEquivalent')
 | 
					const areEquivalent = require('../areEquivalent')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CurrentAbMetadataVersion = 2
 | 
					const CurrentAbMetadataVersion = 2
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
const fs = require('../libs/fsExtra')
 | 
					const fs = require('../../libs/fsExtra')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getPlaylistStr(segmentName, duration, segmentLength, hlsSegmentType, token) {
 | 
					function getPlaylistStr(segmentName, duration, segmentLength, hlsSegmentType, token) {
 | 
				
			||||||
  var ext = hlsSegmentType === 'fmp4' ? 'm4s' : 'ts'
 | 
					  var ext = hlsSegmentType === 'fmp4' ? 'm4s' : 'ts'
 | 
				
			||||||
							
								
								
									
										52
									
								
								server/utils/generators/opmlGenerator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								server/utils/generators/opmlGenerator.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					const xml = require('../../libs/xml')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.generate = (libraryItems, indent = true) => {
 | 
				
			||||||
 | 
					  const bodyItems = []
 | 
				
			||||||
 | 
					  libraryItems.forEach((item) => {
 | 
				
			||||||
 | 
					    if (!item.media.metadata.feedUrl) return
 | 
				
			||||||
 | 
					    const feedAttributes = {
 | 
				
			||||||
 | 
					      type: 'rss',
 | 
				
			||||||
 | 
					      text: item.media.metadata.title,
 | 
				
			||||||
 | 
					      title: item.media.metadata.title,
 | 
				
			||||||
 | 
					      xmlUrl: item.media.metadata.feedUrl
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (item.media.metadata.description) {
 | 
				
			||||||
 | 
					      feedAttributes.description = item.media.metadata.description
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (item.media.metadata.itunesPageUrl) {
 | 
				
			||||||
 | 
					      feedAttributes.htmlUrl = item.media.metadata.itunesPageUrl
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (item.media.metadata.language) {
 | 
				
			||||||
 | 
					      feedAttributes.language = item.media.metadata.language
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    bodyItems.push({
 | 
				
			||||||
 | 
					      outline: {
 | 
				
			||||||
 | 
					        _attr: feedAttributes
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const data = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      opml: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          _attr: {
 | 
				
			||||||
 | 
					            version: '1.0'
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          head: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              title: 'Audiobookshelf Podcast Subscriptions'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          body: bodyItems
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return '<?xml version="1.0" encoding="UTF-8"?>\n' + xml(data, indent)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user