mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Refactor Feed model to create new feed for series
This commit is contained in:
		
							parent
							
								
									d576625cb7
								
							
						
					
					
						commit
						e50bd93958
					
				@ -132,37 +132,39 @@ class RSSFeedController {
 | 
				
			|||||||
   * @param {Response} res
 | 
					   * @param {Response} res
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async openRSSFeedForSeries(req, res) {
 | 
					  async openRSSFeedForSeries(req, res) {
 | 
				
			||||||
    const options = req.body || {}
 | 
					    const reqBody = req.body || {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const series = await Database.seriesModel.findByPk(req.params.seriesId)
 | 
					    const series = await Database.seriesModel.findByPk(req.params.seriesId)
 | 
				
			||||||
    if (!series) return res.sendStatus(404)
 | 
					    if (!series) return res.sendStatus(404)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Check request body options exist
 | 
					    // Check request body options exist
 | 
				
			||||||
    if (!options.serverAddress || !options.slug) {
 | 
					    if (!reqBody.serverAddress || !reqBody.slug || typeof reqBody.serverAddress !== 'string' || typeof reqBody.slug !== 'string') {
 | 
				
			||||||
      Logger.error(`[RSSFeedController] Invalid request body to open RSS feed`)
 | 
					      Logger.error(`[RSSFeedController] Invalid request body to open RSS feed`)
 | 
				
			||||||
      return res.status(400).send('Invalid request body')
 | 
					      return res.status(400).send('Invalid request body')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Check that this slug is not being used for another feed (slug will also be the Feed id)
 | 
					    // Check that this slug is not being used for another feed (slug will also be the Feed id)
 | 
				
			||||||
    if (await this.rssFeedManager.findFeedBySlug(options.slug)) {
 | 
					    if (await this.rssFeedManager.findFeedBySlug(reqBody.slug)) {
 | 
				
			||||||
      Logger.error(`[RSSFeedController] Cannot open RSS feed because slug "${options.slug}" is already in use`)
 | 
					      Logger.error(`[RSSFeedController] Cannot open RSS feed because slug "${reqBody.slug}" is already in use`)
 | 
				
			||||||
      return res.status(400).send('Slug already in use')
 | 
					      return res.status(400).send('Slug already in use')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const seriesJson = series.toOldJSON()
 | 
					    series.books = await series.getBooksExpandedWithLibraryItem()
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get books in series that have audio tracks
 | 
					 | 
				
			||||||
    seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Check series has audio tracks
 | 
					    // Check series has audio tracks
 | 
				
			||||||
    if (!seriesJson.books.length) {
 | 
					    if (!series.books.some((book) => book.includedAudioFiles.length)) {
 | 
				
			||||||
      Logger.error(`[RSSFeedController] Cannot open RSS feed for series "${seriesJson.name}" because it has no audio tracks`)
 | 
					      Logger.error(`[RSSFeedController] Cannot open RSS feed for series "${series.name}" because it has no audio tracks`)
 | 
				
			||||||
      return res.status(400).send('Series has no audio tracks')
 | 
					      return res.status(400).send('Series has no audio tracks')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const feed = await this.rssFeedManager.openFeedForSeries(req.user.id, seriesJson, req.body)
 | 
					    const feed = await this.rssFeedManager.openFeedForSeries(req.user.id, series, req.body)
 | 
				
			||||||
 | 
					    if (!feed) {
 | 
				
			||||||
 | 
					      Logger.error(`[RSSFeedController] Failed to open RSS feed for series "${series.name}"`)
 | 
				
			||||||
 | 
					      return res.status(500).send('Failed to open RSS feed')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
      feed: feed.toJSONMinified()
 | 
					      feed: feed.toOldJSONMinified()
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -285,24 +285,22 @@ class RssFeedManager {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * @param {string} userId
 | 
					   * @param {string} userId
 | 
				
			||||||
   * @param {*} seriesExpanded
 | 
					   * @param {import('../models/Series')} seriesExpanded
 | 
				
			||||||
   * @param {*} options
 | 
					   * @param {*} options
 | 
				
			||||||
   * @returns
 | 
					   * @returns {Promise<import('../models/Feed').FeedExpanded>}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async openFeedForSeries(userId, seriesExpanded, options) {
 | 
					  async openFeedForSeries(userId, seriesExpanded, options) {
 | 
				
			||||||
    const serverAddress = options.serverAddress
 | 
					    const serverAddress = options.serverAddress
 | 
				
			||||||
    const slug = options.slug
 | 
					    const slug = options.slug
 | 
				
			||||||
    const preventIndexing = options.metadataDetails?.preventIndexing ?? true
 | 
					    const feedOptions = this.getFeedOptionsFromReqOptions(options)
 | 
				
			||||||
    const ownerName = options.metadataDetails?.ownerName
 | 
					 | 
				
			||||||
    const ownerEmail = options.metadataDetails?.ownerEmail
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const feed = new Feed()
 | 
					    Logger.info(`[RssFeedManager] Creating RSS feed for series "${seriesExpanded.name}"`)
 | 
				
			||||||
    feed.setFromSeries(userId, slug, seriesExpanded, serverAddress, preventIndexing, ownerName, ownerEmail)
 | 
					    const feedExpanded = await Database.feedModel.createFeedForSeries(userId, seriesExpanded, slug, serverAddress, feedOptions)
 | 
				
			||||||
 | 
					    if (feedExpanded) {
 | 
				
			||||||
    Logger.info(`[RssFeedManager] Opened RSS feed "${feed.feedUrl}"`)
 | 
					      Logger.info(`[RssFeedManager] Opened RSS feed "${feedExpanded.feedURL}"`)
 | 
				
			||||||
    await Database.createFeed(feed)
 | 
					      SocketAuthority.emitter('rss_feed_open', feedExpanded.toOldJSONMinified())
 | 
				
			||||||
    SocketAuthority.emitter('rss_feed_open', feed.toJSONMinified())
 | 
					    }
 | 
				
			||||||
    return feed
 | 
					    return feedExpanded
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async handleCloseFeed(feed) {
 | 
					  async handleCloseFeed(feed) {
 | 
				
			||||||
 | 
				
			|||||||
@ -388,7 +388,73 @@ class Feed extends Model {
 | 
				
			|||||||
    const transaction = await this.sequelize.transaction()
 | 
					    const transaction = await this.sequelize.transaction()
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const feed = await this.create(feedObj, { transaction })
 | 
					      const feed = await this.create(feedObj, { transaction })
 | 
				
			||||||
      feed.feedEpisodes = await feedEpisodeModel.createFromCollectionBooks(collectionExpanded, feed, slug, transaction)
 | 
					      feed.feedEpisodes = await feedEpisodeModel.createFromBooks(booksWithTracks, feed, slug, transaction)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await transaction.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return feed
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      Logger.error(`[Feed] Error creating feed for collection ${collectionExpanded.id}`, error)
 | 
				
			||||||
 | 
					      await transaction.rollback()
 | 
				
			||||||
 | 
					      return null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param {string} userId
 | 
				
			||||||
 | 
					   * @param {import('./Series')} seriesExpanded
 | 
				
			||||||
 | 
					   * @param {string} slug
 | 
				
			||||||
 | 
					   * @param {string} serverAddress
 | 
				
			||||||
 | 
					   * @param {FeedOptions} feedOptions
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @returns {Promise<FeedExpanded>}
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async createFeedForSeries(userId, seriesExpanded, slug, serverAddress, feedOptions) {
 | 
				
			||||||
 | 
					    const booksWithTracks = seriesExpanded.books.filter((book) => book.includedAudioFiles.length)
 | 
				
			||||||
 | 
					    const libraryItemMostRecentlyUpdatedAt = booksWithTracks.reduce((mostRecent, book) => {
 | 
				
			||||||
 | 
					      return book.libraryItem.updatedAt > mostRecent.libraryItem.updatedAt ? book : mostRecent
 | 
				
			||||||
 | 
					    }).libraryItem.updatedAt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const firstBookWithCover = booksWithTracks.find((book) => book.coverPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const allBookAuthorNames = booksWithTracks.reduce((authorNames, book) => {
 | 
				
			||||||
 | 
					      const bookAuthorsToAdd = book.authors.filter((author) => !authorNames.includes(author.name)).map((author) => author.name)
 | 
				
			||||||
 | 
					      return authorNames.concat(bookAuthorsToAdd)
 | 
				
			||||||
 | 
					    }, [])
 | 
				
			||||||
 | 
					    let author = allBookAuthorNames.slice(0, 3).join(', ')
 | 
				
			||||||
 | 
					    if (allBookAuthorNames.length > 3) {
 | 
				
			||||||
 | 
					      author += ' & more'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const feedObj = {
 | 
				
			||||||
 | 
					      slug,
 | 
				
			||||||
 | 
					      entityType: 'series',
 | 
				
			||||||
 | 
					      entityId: seriesExpanded.id,
 | 
				
			||||||
 | 
					      entityUpdatedAt: libraryItemMostRecentlyUpdatedAt,
 | 
				
			||||||
 | 
					      serverAddress,
 | 
				
			||||||
 | 
					      feedURL: `/feed/${slug}`,
 | 
				
			||||||
 | 
					      imageURL: firstBookWithCover?.coverPath ? `/feed/${slug}/cover${Path.extname(firstBookWithCover.coverPath)}` : `/Logo.png`,
 | 
				
			||||||
 | 
					      siteURL: `/library/${booksWithTracks[0].libraryItem.libraryId}/series/${seriesExpanded.id}`,
 | 
				
			||||||
 | 
					      title: seriesExpanded.name,
 | 
				
			||||||
 | 
					      description: seriesExpanded.description || '',
 | 
				
			||||||
 | 
					      author,
 | 
				
			||||||
 | 
					      podcastType: 'serial',
 | 
				
			||||||
 | 
					      preventIndexing: feedOptions.preventIndexing,
 | 
				
			||||||
 | 
					      ownerName: feedOptions.ownerName,
 | 
				
			||||||
 | 
					      ownerEmail: feedOptions.ownerEmail,
 | 
				
			||||||
 | 
					      explicit: booksWithTracks.some((book) => book.explicit), // If any book is explicit, the feed is explicit
 | 
				
			||||||
 | 
					      coverPath: firstBookWithCover?.coverPath || null,
 | 
				
			||||||
 | 
					      userId
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @type {typeof import('./FeedEpisode')} */
 | 
				
			||||||
 | 
					    const feedEpisodeModel = this.sequelize.models.feedEpisode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const transaction = await this.sequelize.transaction()
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const feed = await this.create(feedObj, { transaction })
 | 
				
			||||||
 | 
					      feed.feedEpisodes = await feedEpisodeModel.createFromBooks(booksWithTracks, feed, slug, transaction)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await transaction.commit()
 | 
					      await transaction.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -216,21 +216,19 @@ class FeedEpisode extends Model {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * @param {import('./Collection')} collectionExpanded
 | 
					   * @param {import('./Book')[]} books
 | 
				
			||||||
   * @param {import('./Feed')} feed
 | 
					   * @param {import('./Feed')} feed
 | 
				
			||||||
   * @param {string} slug
 | 
					   * @param {string} slug
 | 
				
			||||||
   * @param {import('sequelize').Transaction} transaction
 | 
					   * @param {import('sequelize').Transaction} transaction
 | 
				
			||||||
   * @returns {Promise<FeedEpisode[]>}
 | 
					   * @returns {Promise<FeedEpisode[]>}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  static async createFromCollectionBooks(collectionExpanded, feed, slug, transaction) {
 | 
					  static async createFromBooks(books, feed, slug, transaction) {
 | 
				
			||||||
    const booksWithTracks = collectionExpanded.books.filter((book) => book.includedAudioFiles.length)
 | 
					    const earliestLibraryItemCreatedAt = books.reduce((earliest, book) => {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const earliestLibraryItemCreatedAt = collectionExpanded.books.reduce((earliest, book) => {
 | 
					 | 
				
			||||||
      return book.libraryItem.createdAt < earliest.libraryItem.createdAt ? book : earliest
 | 
					      return book.libraryItem.createdAt < earliest.libraryItem.createdAt ? book : earliest
 | 
				
			||||||
    }).libraryItem.createdAt
 | 
					    }).libraryItem.createdAt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const feedEpisodeObjs = []
 | 
					    const feedEpisodeObjs = []
 | 
				
			||||||
    for (const book of booksWithTracks) {
 | 
					    for (const book of books) {
 | 
				
			||||||
      const useChapterTitles = this.checkUseChapterTitlesForEpisodes(book)
 | 
					      const useChapterTitles = this.checkUseChapterTitlesForEpisodes(book)
 | 
				
			||||||
      for (const track of book.trackList) {
 | 
					      for (const track of book.trackList) {
 | 
				
			||||||
        feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(book, earliestLibraryItemCreatedAt, feed, slug, track, useChapterTitles))
 | 
					        feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(book, earliestLibraryItemCreatedAt, feed, slug, track, useChapterTitles))
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
const { DataTypes, Model, where, fn, col } = require('sequelize')
 | 
					const { DataTypes, Model, where, fn, col, literal } = require('sequelize')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { getTitlePrefixAtEnd } = require('../utils/index')
 | 
					const { getTitlePrefixAtEnd } = require('../utils/index')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -20,6 +20,11 @@ class Series extends Model {
 | 
				
			|||||||
    this.createdAt
 | 
					    this.createdAt
 | 
				
			||||||
    /** @type {Date} */
 | 
					    /** @type {Date} */
 | 
				
			||||||
    this.updatedAt
 | 
					    this.updatedAt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Expanded properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @type {import('./Book').BookExpandedWithLibraryItem[]} - only set when expanded */
 | 
				
			||||||
 | 
					    this.books
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@ -103,6 +108,35 @@ class Series extends Model {
 | 
				
			|||||||
    Series.belongsTo(library)
 | 
					    Series.belongsTo(library)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get all books in collection expanded with library item
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @returns {Promise<import('./Book').BookExpandedWithLibraryItem[]>}
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  getBooksExpandedWithLibraryItem() {
 | 
				
			||||||
 | 
					    return this.getBooks({
 | 
				
			||||||
 | 
					      joinTableAttributes: ['sequence'],
 | 
				
			||||||
 | 
					      include: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          model: this.sequelize.models.libraryItem
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          model: this.sequelize.models.author,
 | 
				
			||||||
 | 
					          through: {
 | 
				
			||||||
 | 
					            attributes: []
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          model: this.sequelize.models.series,
 | 
				
			||||||
 | 
					          through: {
 | 
				
			||||||
 | 
					            attributes: ['sequence']
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      order: [[literal('CAST(`bookSeries.sequence` AS FLOAT) ASC NULLS LAST')]]
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toOldJSON() {
 | 
					  toOldJSON() {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      id: this.id,
 | 
					      id: this.id,
 | 
				
			||||||
 | 
				
			|||||||
@ -182,65 +182,6 @@ class Feed {
 | 
				
			|||||||
    this.updatedAt = Date.now()
 | 
					    this.updatedAt = Date.now()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setFromSeries(userId, slug, seriesExpanded, serverAddress, preventIndexing = true, ownerName = null, ownerEmail = null) {
 | 
					 | 
				
			||||||
    const feedUrl = `/feed/${slug}`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let itemsWithTracks = seriesExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length)
 | 
					 | 
				
			||||||
    // Sort series items by series sequence
 | 
					 | 
				
			||||||
    itemsWithTracks = naturalSort(itemsWithTracks).asc((li) => li.media.metadata.getSeriesSequence(seriesExpanded.id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const libraryId = itemsWithTracks[0].libraryId
 | 
					 | 
				
			||||||
    const firstItemWithCover = itemsWithTracks.find((li) => li.media.coverPath)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.id = uuidv4()
 | 
					 | 
				
			||||||
    this.slug = slug
 | 
					 | 
				
			||||||
    this.userId = userId
 | 
					 | 
				
			||||||
    this.entityType = 'series'
 | 
					 | 
				
			||||||
    this.entityId = seriesExpanded.id
 | 
					 | 
				
			||||||
    this.entityUpdatedAt = seriesExpanded.updatedAt // This will be set to the most recently updated library item
 | 
					 | 
				
			||||||
    this.coverPath = firstItemWithCover?.media.coverPath || null
 | 
					 | 
				
			||||||
    this.serverAddress = serverAddress
 | 
					 | 
				
			||||||
    this.feedUrl = feedUrl
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const coverFileExtension = this.coverPath ? Path.extname(this.coverPath) : null
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.meta = new FeedMeta()
 | 
					 | 
				
			||||||
    this.meta.title = seriesExpanded.name
 | 
					 | 
				
			||||||
    this.meta.description = seriesExpanded.description || ''
 | 
					 | 
				
			||||||
    this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks)
 | 
					 | 
				
			||||||
    this.meta.imageUrl = this.coverPath ? `/feed/${slug}/cover${coverFileExtension}` : `/Logo.png`
 | 
					 | 
				
			||||||
    this.meta.feedUrl = feedUrl
 | 
					 | 
				
			||||||
    this.meta.link = `/library/${libraryId}/series/${seriesExpanded.id}`
 | 
					 | 
				
			||||||
    this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
 | 
					 | 
				
			||||||
    this.meta.preventIndexing = preventIndexing
 | 
					 | 
				
			||||||
    this.meta.ownerName = ownerName
 | 
					 | 
				
			||||||
    this.meta.ownerEmail = ownerEmail
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.episodes = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Used for calculating pubdate
 | 
					 | 
				
			||||||
    const earliestItemAddedAt = itemsWithTracks.reduce((earliest, item) => (item.addedAt < earliest ? item.addedAt : earliest), itemsWithTracks[0].addedAt)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    itemsWithTracks.forEach((item, index) => {
 | 
					 | 
				
			||||||
      if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
 | 
					 | 
				
			||||||
      item.media.tracks.forEach((audioTrack) => {
 | 
					 | 
				
			||||||
        const feedEpisode = new FeedEpisode()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Offset pubdate to ensure correct order
 | 
					 | 
				
			||||||
        let trackTimeOffset = isNaN(audioTrack.index) ? 0 : Number(audioTrack.index) * 1000 // Offset track
 | 
					 | 
				
			||||||
        trackTimeOffset += index * 1000 // Offset item
 | 
					 | 
				
			||||||
        const episodePubDateOverride = date.format(new Date(earliestItemAddedAt + trackTimeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
 | 
					 | 
				
			||||||
        feedEpisode.setFromAudiobookTrack(item, serverAddress, slug, audioTrack, this.meta, useChapterTitles, episodePubDateOverride)
 | 
					 | 
				
			||||||
        this.episodes.push(feedEpisode)
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.createdAt = Date.now()
 | 
					 | 
				
			||||||
    this.updatedAt = Date.now()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  updateFromSeries(seriesExpanded) {
 | 
					  updateFromSeries(seriesExpanded) {
 | 
				
			||||||
    let itemsWithTracks = seriesExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length)
 | 
					    let itemsWithTracks = seriesExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length)
 | 
				
			||||||
    // Sort series items by series sequence
 | 
					    // Sort series items by series sequence
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user