mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-07-30 15:55:26 +02:00
Add:Persist RSS feeds in db #696, Update:RSS feed data model
This commit is contained in:
parent
881baa818d
commit
fbbceaa642
9 changed files with 366 additions and 130 deletions
118
server/objects/Feed.js
Normal file
118
server/objects/Feed.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
const FeedMeta = require('./FeedMeta')
|
||||
const FeedEpisode = require('./FeedEpisode')
|
||||
const { Podcast } = require('podcast')
|
||||
|
||||
class Feed {
|
||||
constructor(feed) {
|
||||
this.id = null
|
||||
this.slug = null
|
||||
this.userId = null
|
||||
this.entityType = null
|
||||
this.entityId = null
|
||||
|
||||
this.coverPath = null
|
||||
this.serverAddress = null
|
||||
this.feedUrl = null
|
||||
|
||||
this.meta = null
|
||||
this.episodes = null
|
||||
|
||||
this.createdAt = null
|
||||
this.updatedAt = null
|
||||
|
||||
if (feed) {
|
||||
this.construct(feed)
|
||||
}
|
||||
}
|
||||
|
||||
construct(feed) {
|
||||
this.id = feed.id
|
||||
this.slug = feed.slug
|
||||
this.userId = feed.userId
|
||||
this.entityType = feed.entityType
|
||||
this.entityId = feed.entityId
|
||||
this.coverPath = feed.coverPath
|
||||
this.serverAddress = feed.serverAddress
|
||||
this.feedUrl = feed.feedUrl
|
||||
this.meta = new FeedMeta(feed.meta)
|
||||
this.episodes = feed.episodes.map(ep => new FeedEpisode(ep))
|
||||
this.createdAt = feed.createdAt
|
||||
this.updatedAt = feed.updatedAt
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
slug: this.slug,
|
||||
userId: this.userId,
|
||||
entityType: this.entityType,
|
||||
entityId: this.entityId,
|
||||
coverPath: this.coverPath,
|
||||
serverAddress: this.serverAddress,
|
||||
feedUrl: this.feedUrl,
|
||||
meta: this.meta.toJSON(),
|
||||
episodes: this.episodes.map(ep => ep.toJSON()),
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
getEpisodePath(id) {
|
||||
var episode = this.episodes.find(ep => ep.id === id)
|
||||
if (!episode) return null
|
||||
return episode.fullPath
|
||||
}
|
||||
|
||||
setFromItem(userId, slug, libraryItem, serverAddress) {
|
||||
const media = libraryItem.media
|
||||
const mediaMetadata = media.metadata
|
||||
const isPodcast = libraryItem.mediaType === 'podcast'
|
||||
|
||||
const feedUrl = `${serverAddress}/feed/${slug}`
|
||||
const author = isPodcast ? mediaMetadata.author : mediaMetadata.authorName
|
||||
|
||||
this.id = slug
|
||||
this.slug = slug
|
||||
this.userId = userId
|
||||
this.entityType = 'item'
|
||||
this.entityId = libraryItem.id
|
||||
this.coverPath = media.coverPath || null
|
||||
this.serverAddress = serverAddress
|
||||
this.feedUrl = feedUrl
|
||||
|
||||
this.meta = new FeedMeta()
|
||||
this.meta.title = mediaMetadata.title
|
||||
this.meta.description = mediaMetadata.description
|
||||
this.meta.author = author
|
||||
this.meta.imageUrl = media.coverPath ? `${serverAddress}/feed/${slug}/cover` : `${serverAddress}/Logo.png`
|
||||
this.meta.feedUrl = feedUrl
|
||||
this.meta.link = `${serverAddress}/items/${libraryItem.id}`
|
||||
|
||||
this.episodes = []
|
||||
if (isPodcast) { // PODCAST EPISODES
|
||||
media.episodes.forEach((episode) => {
|
||||
var feedEpisode = new FeedEpisode()
|
||||
feedEpisode.setFromPodcastEpisode(libraryItem, serverAddress, slug, episode, this.meta)
|
||||
this.episodes.push(feedEpisode)
|
||||
})
|
||||
} else { // AUDIOBOOK EPISODES
|
||||
media.tracks.forEach((audioTrack) => {
|
||||
var feedEpisode = new FeedEpisode()
|
||||
feedEpisode.setFromAudiobookTrack(libraryItem, serverAddress, slug, audioTrack, this.meta)
|
||||
this.episodes.push(feedEpisode)
|
||||
})
|
||||
}
|
||||
|
||||
this.createdAt = Date.now()
|
||||
this.updatedAt = Date.now()
|
||||
}
|
||||
|
||||
buildXml() {
|
||||
const pod = new Podcast(this.meta.getPodcastMeta())
|
||||
this.episodes.forEach((ep) => {
|
||||
pod.addItem(ep.getPodcastEpisode())
|
||||
})
|
||||
return pod.buildXml()
|
||||
}
|
||||
}
|
||||
module.exports = Feed
|
130
server/objects/FeedEpisode.js
Normal file
130
server/objects/FeedEpisode.js
Normal file
|
@ -0,0 +1,130 @@
|
|||
const Path = require('path')
|
||||
const date = require('date-and-time')
|
||||
|
||||
class FeedEpisode {
|
||||
constructor(episode) {
|
||||
this.id = null
|
||||
|
||||
this.title = null
|
||||
this.description = null
|
||||
this.enclosure = null
|
||||
this.pubDate = null
|
||||
this.link = null
|
||||
this.author = null
|
||||
this.explicit = null
|
||||
this.duration = null
|
||||
|
||||
this.libraryItemId = null
|
||||
this.episodeId = null
|
||||
this.trackIndex = null
|
||||
this.fullPath = null
|
||||
|
||||
if (episode) {
|
||||
this.construct(episode)
|
||||
}
|
||||
}
|
||||
|
||||
construct(episode) {
|
||||
this.id = episode.id
|
||||
this.title = episode.title
|
||||
this.description = episode.description
|
||||
this.enclosure = episode.enclosure ? { ...episode.enclosure } : null
|
||||
this.pubDate = episode.pubDate
|
||||
this.link = episode.link
|
||||
this.author = episode.author
|
||||
this.explicit = episode.explicit
|
||||
this.duration = episode.duration
|
||||
this.libraryItemId = episode.libraryItemId
|
||||
this.episodeId = episode.episodeId || null
|
||||
this.trackIndex = episode.trackIndex || 0
|
||||
this.fullPath = episode.fullPath
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
enclosure: this.enclosure ? { ...this.enclosure } : null,
|
||||
pubDate: this.pubDate,
|
||||
link: this.link,
|
||||
author: this.author,
|
||||
explicit: this.explicit,
|
||||
duration: this.duration,
|
||||
libraryItemId: this.libraryItemId,
|
||||
episodeId: this.episodeId,
|
||||
trackIndex: this.trackIndex,
|
||||
fullPath: this.fullPath
|
||||
}
|
||||
}
|
||||
|
||||
setFromPodcastEpisode(libraryItem, serverAddress, slug, episode, meta) {
|
||||
const contentUrl = `/feed/${slug}/item/${episode.id}/${episode.audioFile.metadata.filename}`
|
||||
const media = libraryItem.media
|
||||
const mediaMetadata = media.metadata
|
||||
|
||||
this.id = episode.id
|
||||
this.title = episode.title
|
||||
this.description = episode.description || ''
|
||||
this.enclosure = {
|
||||
url: `${serverAddress}${contentUrl}`,
|
||||
type: episode.audioTrack.mimeType,
|
||||
size: episode.size
|
||||
}
|
||||
this.pubDate = episode.pubDate
|
||||
this.link = meta.link
|
||||
this.author = meta.author
|
||||
this.explicit = mediaMetadata.explicit
|
||||
this.duration = episode.duration
|
||||
this.libraryItemId = libraryItem.id
|
||||
this.episodeId = episode.id
|
||||
this.trackIndex = 0
|
||||
this.fullPath = episode.audioFile.metadata.path
|
||||
}
|
||||
|
||||
setFromAudiobookTrack(libraryItem, serverAddress, slug, audioTrack, meta) {
|
||||
// Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate>
|
||||
const audiobookPubDate = date.format(new Date(libraryItem.addedAt), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
|
||||
|
||||
const contentUrl = `/feed/${slug}/item/${audioTrack.index}/${audioTrack.metadata.filename}`
|
||||
const media = libraryItem.media
|
||||
const mediaMetadata = media.metadata
|
||||
|
||||
var title = audioTrack.title
|
||||
if (libraryItem.media.chapters.length) {
|
||||
// If audio track start and chapter start are within 1 seconds of eachother then use the chapter title
|
||||
var matchingChapter = libraryItem.media.chapters.find(ch => Math.abs(ch.start - audioTrack.startOffset) < 1)
|
||||
if (matchingChapter && matchingChapter.title) title = matchingChapter.title
|
||||
}
|
||||
|
||||
this.id = String(audioTrack.index)
|
||||
this.title = title
|
||||
this.description = mediaMetadata.description || ''
|
||||
this.enclosure = {
|
||||
url: `${serverAddress}${contentUrl}`,
|
||||
type: audioTrack.mimeType,
|
||||
size: audioTrack.metadata.size
|
||||
}
|
||||
this.pubDate = audiobookPubDate
|
||||
this.link = meta.link
|
||||
this.author = meta.author
|
||||
this.explicit = mediaMetadata.explicit
|
||||
this.duration = audioTrack.duration
|
||||
this.libraryItemId = libraryItem.id
|
||||
this.episodeId = null
|
||||
this.trackIndex = audioTrack.index
|
||||
this.fullPath = audioTrack.metadata.path
|
||||
}
|
||||
|
||||
getPodcastEpisode() {
|
||||
return {
|
||||
title: this.title,
|
||||
description: this.description || '',
|
||||
enclosure: this.enclosure,
|
||||
date: this.pubDate || '',
|
||||
url: this.link,
|
||||
author: this.author
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = FeedEpisode
|
47
server/objects/FeedMeta.js
Normal file
47
server/objects/FeedMeta.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
class FeedMeta {
|
||||
constructor(meta) {
|
||||
this.title = null
|
||||
this.description = null
|
||||
this.author = null
|
||||
this.imageUrl = null
|
||||
this.feedUrl = null
|
||||
this.link = null
|
||||
|
||||
if (meta) {
|
||||
this.construct(meta)
|
||||
}
|
||||
}
|
||||
|
||||
construct(meta) {
|
||||
this.title = meta.title
|
||||
this.description = meta.description
|
||||
this.author = meta.author
|
||||
this.imageUrl = meta.imageUrl
|
||||
this.feedUrl = meta.feedUrl
|
||||
this.link = meta.link
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
author: this.author,
|
||||
imageUrl: this.imageUrl,
|
||||
feedUrl: this.feedUrl,
|
||||
link: this.link
|
||||
}
|
||||
}
|
||||
|
||||
getPodcastMeta() {
|
||||
return {
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
feedUrl: this.feedUrl,
|
||||
siteUrl: this.link,
|
||||
imageUrl: this.imageUrl,
|
||||
author: this.author || 'advplyr',
|
||||
language: 'en'
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = FeedMeta
|
Loading…
Add table
Add a link
Reference in a new issue