mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add remaining personalized shelf queries for podcasts
This commit is contained in:
		
							parent
							
								
									09eefae808
								
							
						
					
					
						commit
						91b6c4412d
					
				@ -170,9 +170,8 @@ export default {
 | 
			
		||||
      this.loaded = true
 | 
			
		||||
    },
 | 
			
		||||
    async fetchCategories() {
 | 
			
		||||
      const endpoint = this.currentLibraryMediaType === 'book' ? 'personalized2' : 'personalized'
 | 
			
		||||
      const categories = await this.$axios
 | 
			
		||||
        .$get(`/api/libraries/${this.currentLibraryId}/${endpoint}?include=rssfeed,numEpisodesIncomplete`)
 | 
			
		||||
        .$get(`/api/libraries/${this.currentLibraryId}/personalized2?include=rssfeed,numEpisodesIncomplete`)
 | 
			
		||||
        .then((data) => {
 | 
			
		||||
          return data
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
@ -133,12 +133,15 @@ export default {
 | 
			
		||||
      this.rendition.spread(settings.spread || 'auto')
 | 
			
		||||
    },
 | 
			
		||||
    prev() {
 | 
			
		||||
      if (!this.rendition?.manager) return
 | 
			
		||||
      return this.rendition?.prev()
 | 
			
		||||
    },
 | 
			
		||||
    next() {
 | 
			
		||||
      if (!this.rendition?.manager) return
 | 
			
		||||
      return this.rendition?.next()
 | 
			
		||||
    },
 | 
			
		||||
    goToChapter(href) {
 | 
			
		||||
      if (!this.rendition?.manager) return
 | 
			
		||||
      return this.rendition?.display(href)
 | 
			
		||||
    },
 | 
			
		||||
    keyUp(e) {
 | 
			
		||||
 | 
			
		||||
@ -446,27 +446,27 @@ module.exports = (sequelize) => {
 | 
			
		||||
     * @returns {object[]} array of shelf objects
 | 
			
		||||
     */
 | 
			
		||||
    static async getPersonalizedShelves(library, userId, include, limit) {
 | 
			
		||||
      const isPodcastLibrary = library.mediaType === 'podcast'
 | 
			
		||||
 | 
			
		||||
      const fullStart = Date.now() // Used for testing load times
 | 
			
		||||
 | 
			
		||||
      const shelves = []
 | 
			
		||||
 | 
			
		||||
      // "Continue Listening" shelf
 | 
			
		||||
      const itemsInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, userId, include, limit, false)
 | 
			
		||||
      if (itemsInProgressPayload.items.length) {
 | 
			
		||||
        shelves.push({
 | 
			
		||||
          id: 'continue-listening',
 | 
			
		||||
          label: 'Continue Listening',
 | 
			
		||||
          labelStringKey: 'LabelContinueListening',
 | 
			
		||||
          type: isPodcastLibrary ? 'episode' : 'book',
 | 
			
		||||
          type: library.isPodcast ? 'episode' : 'book',
 | 
			
		||||
          entities: itemsInProgressPayload.items,
 | 
			
		||||
          total: itemsInProgressPayload.count
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      Logger.debug(`Loaded ${itemsInProgressPayload.items.length} items for "Continue Listening" in ${((Date.now() - fullStart) / 1000).toFixed(2)}s`)
 | 
			
		||||
      Logger.debug(`Loaded ${itemsInProgressPayload.items.length} of ${itemsInProgressPayload.count} items for "Continue Listening" in ${((Date.now() - fullStart) / 1000).toFixed(2)}s`)
 | 
			
		||||
 | 
			
		||||
      let start = Date.now()
 | 
			
		||||
      if (library.mediaType === 'book') {
 | 
			
		||||
      if (library.isBook) {
 | 
			
		||||
        // "Continue Reading" shelf
 | 
			
		||||
        const ebooksInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, userId, include, limit, true)
 | 
			
		||||
        if (ebooksInProgressPayload.items.length) {
 | 
			
		||||
          shelves.push({
 | 
			
		||||
@ -478,9 +478,10 @@ module.exports = (sequelize) => {
 | 
			
		||||
            total: ebooksInProgressPayload.count
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
        Logger.debug(`Loaded ${ebooksInProgressPayload.items.length} items for "Continue Reading" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
        Logger.debug(`Loaded ${ebooksInProgressPayload.items.length} of ${ebooksInProgressPayload.count} items for "Continue Reading" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
 | 
			
		||||
        start = Date.now()
 | 
			
		||||
        // "Continue Series" shelf
 | 
			
		||||
        const continueSeriesPayload = await libraryFilters.getLibraryItemsContinueSeries(library, userId, include, limit)
 | 
			
		||||
        if (continueSeriesPayload.libraryItems.length) {
 | 
			
		||||
          shelves.push({
 | 
			
		||||
@ -492,10 +493,25 @@ module.exports = (sequelize) => {
 | 
			
		||||
            total: continueSeriesPayload.count
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
        Logger.debug(`Loaded ${continueSeriesPayload.libraryItems.length} items for "Continue Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
        start = Date.now()
 | 
			
		||||
        Logger.debug(`Loaded ${continueSeriesPayload.libraryItems.length} of ${continueSeriesPayload.count} items for "Continue Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
      } else if (library.isPodcast) {
 | 
			
		||||
        // "Newest Episodes" shelf
 | 
			
		||||
        const newestEpisodesPayload = await libraryFilters.getNewestPodcastEpisodes(library, userId, limit)
 | 
			
		||||
        if (newestEpisodesPayload.libraryItems.length) {
 | 
			
		||||
          shelves.push({
 | 
			
		||||
            id: 'newest-episodes',
 | 
			
		||||
            label: 'Newest Episodes',
 | 
			
		||||
            labelStringKey: 'LabelNewestEpisodes',
 | 
			
		||||
            type: 'episode',
 | 
			
		||||
            entities: newestEpisodesPayload.libraryItems,
 | 
			
		||||
            total: newestEpisodesPayload.count
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
        Logger.debug(`Loaded ${newestEpisodesPayload.libraryItems.length} of ${newestEpisodesPayload.count} episodes for "Newest Episodes" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      start = Date.now()
 | 
			
		||||
      // "Recently Added" shelf
 | 
			
		||||
      const mostRecentPayload = await libraryFilters.getLibraryItemsMostRecentlyAdded(library, userId, include, limit)
 | 
			
		||||
      if (mostRecentPayload.libraryItems.length) {
 | 
			
		||||
        shelves.push({
 | 
			
		||||
@ -507,9 +523,11 @@ module.exports = (sequelize) => {
 | 
			
		||||
          total: mostRecentPayload.count
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      Logger.debug(`Loaded ${mostRecentPayload.libraryItems.length} items for "Recently Added" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
      Logger.debug(`Loaded ${mostRecentPayload.libraryItems.length} of ${mostRecentPayload.count} items for "Recently Added" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
 | 
			
		||||
      if (library.isBook) {
 | 
			
		||||
        start = Date.now()
 | 
			
		||||
        // "Recent Series" shelf
 | 
			
		||||
        const seriesMostRecentPayload = await libraryFilters.getSeriesMostRecentlyAdded(library, include, 5)
 | 
			
		||||
        if (seriesMostRecentPayload.series.length) {
 | 
			
		||||
          shelves.push({
 | 
			
		||||
@ -521,9 +539,10 @@ module.exports = (sequelize) => {
 | 
			
		||||
            total: seriesMostRecentPayload.count
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      Logger.debug(`Loaded ${seriesMostRecentPayload.series.length} items for "Recent Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
        Logger.debug(`Loaded ${seriesMostRecentPayload.series.length} of ${seriesMostRecentPayload.count} series for "Recent Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
 | 
			
		||||
        start = Date.now()
 | 
			
		||||
        // "Discover" shelf
 | 
			
		||||
        const discoverLibraryItemsPayload = await libraryFilters.getLibraryItemsToDiscover(library, userId, include, limit)
 | 
			
		||||
        if (discoverLibraryItemsPayload.libraryItems.length) {
 | 
			
		||||
          shelves.push({
 | 
			
		||||
@ -535,23 +554,27 @@ module.exports = (sequelize) => {
 | 
			
		||||
            total: discoverLibraryItemsPayload.count
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      Logger.debug(`Loaded ${discoverLibraryItemsPayload.libraryItems.length} items for "Discover" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
        Logger.debug(`Loaded ${discoverLibraryItemsPayload.libraryItems.length} of ${discoverLibraryItemsPayload.count} items for "Discover" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      start = Date.now()
 | 
			
		||||
      // "Listen Again" shelf
 | 
			
		||||
      const listenAgainPayload = await libraryFilters.getMediaFinished(library, userId, include, limit, false)
 | 
			
		||||
      if (listenAgainPayload.items.length) {
 | 
			
		||||
        shelves.push({
 | 
			
		||||
          id: 'listen-again',
 | 
			
		||||
          label: 'Listen Again',
 | 
			
		||||
          labelStringKey: 'LabelListenAgain',
 | 
			
		||||
          type: isPodcastLibrary ? 'episode' : 'book',
 | 
			
		||||
          type: library.isPodcast ? 'episode' : 'book',
 | 
			
		||||
          entities: listenAgainPayload.items,
 | 
			
		||||
          total: listenAgainPayload.count
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      Logger.debug(`Loaded ${listenAgainPayload.items.length} items for "Listen Again" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
      Logger.debug(`Loaded ${listenAgainPayload.items.length} of ${listenAgainPayload.count} items for "Listen Again" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
 | 
			
		||||
      if (library.isBook) {
 | 
			
		||||
        start = Date.now()
 | 
			
		||||
        // "Read Again" shelf
 | 
			
		||||
        const readAgainPayload = await libraryFilters.getMediaFinished(library, userId, include, limit, true)
 | 
			
		||||
        if (readAgainPayload.items.length) {
 | 
			
		||||
          shelves.push({
 | 
			
		||||
@ -563,9 +586,10 @@ module.exports = (sequelize) => {
 | 
			
		||||
            total: readAgainPayload.count
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      Logger.debug(`Loaded ${readAgainPayload.items.length} items for "Read Again" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
        Logger.debug(`Loaded ${readAgainPayload.items.length} of ${readAgainPayload.count} items for "Read Again" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
 | 
			
		||||
        start = Date.now()
 | 
			
		||||
        // "Newest Authors" shelf
 | 
			
		||||
        const newestAuthorsPayload = await libraryFilters.getNewestAuthors(library, limit)
 | 
			
		||||
        if (newestAuthorsPayload.authors.length) {
 | 
			
		||||
          shelves.push({
 | 
			
		||||
@ -577,7 +601,8 @@ module.exports = (sequelize) => {
 | 
			
		||||
            total: newestAuthorsPayload.count
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      Logger.debug(`Loaded ${newestAuthorsPayload.authors.length} authors for "Newest Authors" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
        Logger.debug(`Loaded ${newestAuthorsPayload.authors.length} of ${newestAuthorsPayload.count} authors for "Newest Authors" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      Logger.debug(`Loaded ${shelves.length} personalized shelves in ${((Date.now() - fullStart) / 1000).toFixed(2)}s`)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -59,10 +59,14 @@ module.exports = {
 | 
			
		||||
        count
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // TODO: Get episodes in progress
 | 
			
		||||
      const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredPodcastEpisodes(library.id, userId, 'progress', 'in-progress', 'progress', true, limit, 0)
 | 
			
		||||
      return {
 | 
			
		||||
        count: 0,
 | 
			
		||||
        items: []
 | 
			
		||||
        count,
 | 
			
		||||
        items: libraryItems.map(li => {
 | 
			
		||||
          const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
			
		||||
          oldLibraryItem.recentEpisode = li.recentEpisode
 | 
			
		||||
          return oldLibraryItem
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
@ -160,10 +164,14 @@ module.exports = {
 | 
			
		||||
        count
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // TODO: Get podcast episodes finished
 | 
			
		||||
      const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredPodcastEpisodes(library.id, userId, 'progress', 'finished', 'progress', true, limit, 0)
 | 
			
		||||
      return {
 | 
			
		||||
        items: [],
 | 
			
		||||
        count: 0
 | 
			
		||||
        count,
 | 
			
		||||
        items: libraryItems.map(li => {
 | 
			
		||||
          const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
			
		||||
          oldLibraryItem.recentEpisode = li.recentEpisode
 | 
			
		||||
          return oldLibraryItem
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
@ -299,5 +307,26 @@ module.exports = {
 | 
			
		||||
      }),
 | 
			
		||||
      count
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get podcast episodes most recently added
 | 
			
		||||
   * @param {oldLibrary} library 
 | 
			
		||||
   * @param {string} userId 
 | 
			
		||||
   * @param {number} limit 
 | 
			
		||||
   * @returns {object} {libraryItems:oldLibraryItem[], count:number}
 | 
			
		||||
   */
 | 
			
		||||
  async getNewestPodcastEpisodes(library, userId, limit) {
 | 
			
		||||
    if (library.mediaType !== 'podcast') return { libraryItems: [], count: 0 }
 | 
			
		||||
 | 
			
		||||
    const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredPodcastEpisodes(library.id, userId, null, null, 'createdAt', true, limit, 0)
 | 
			
		||||
    return {
 | 
			
		||||
      count,
 | 
			
		||||
      libraryItems: libraryItems.map(li => {
 | 
			
		||||
        const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
			
		||||
        oldLibraryItem.recentEpisode = li.recentEpisode
 | 
			
		||||
        return oldLibraryItem
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -230,7 +230,7 @@ module.exports = {
 | 
			
		||||
      }
 | 
			
		||||
    } else if (sortBy === 'sequence') {
 | 
			
		||||
      const nullDir = sortDesc ? 'DESC NULLS FIRST' : 'ASC NULLS LAST'
 | 
			
		||||
      return [[Sequelize.literal(`CAST(\`series.bookSeries.sequence\` AS INTEGER) COLLATE NOCASE ${nullDir}`)]]
 | 
			
		||||
      return [[Sequelize.literal(`CAST(\`series.bookSeries.sequence\` AS INTEGER) ${nullDir}`)]]
 | 
			
		||||
    } else if (sortBy === 'progress') {
 | 
			
		||||
      return [[Sequelize.literal('mediaProgresses.updatedAt'), dir]]
 | 
			
		||||
    }
 | 
			
		||||
@ -268,7 +268,7 @@ module.exports = {
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      order: [
 | 
			
		||||
        Sequelize.literal('CAST(`books.bookSeries.sequence` AS INTEGER) COLLATE NOCASE ASC NULLS LAST')
 | 
			
		||||
        Sequelize.literal('CAST(`books.bookSeries.sequence` AS INTEGER) ASC NULLS LAST')
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
    const bookSeriesToInclude = []
 | 
			
		||||
@ -426,7 +426,7 @@ module.exports = {
 | 
			
		||||
      })
 | 
			
		||||
      if (sortBy !== 'sequence') {
 | 
			
		||||
        // Secondary sort by sequence
 | 
			
		||||
        sortOrder.push([Sequelize.literal('CAST(`series.bookSeries.sequence` AS INTEGER) COLLATE NOCASE ASC NULLS LAST')])
 | 
			
		||||
        sortOrder.push([Sequelize.literal('CAST(`series.bookSeries.sequence` AS INTEGER) ASC NULLS LAST')])
 | 
			
		||||
      }
 | 
			
		||||
    } else if (filterGroup === 'issues') {
 | 
			
		||||
      libraryItemWhere[Sequelize.Op.or] = [
 | 
			
		||||
 | 
			
		||||
@ -148,6 +148,96 @@ module.exports = {
 | 
			
		||||
      return libraryItem
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      libraryItems,
 | 
			
		||||
      count
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get podcast episodes filtered and sorted
 | 
			
		||||
   * @param {string} libraryId 
 | 
			
		||||
   * @param {string} userId 
 | 
			
		||||
   * @param {[string]} filterGroup 
 | 
			
		||||
   * @param {[string]} filterValue 
 | 
			
		||||
   * @param {string} sortBy 
 | 
			
		||||
   * @param {string} sortDesc 
 | 
			
		||||
   * @param {number} limit 
 | 
			
		||||
   * @param {number} offset 
 | 
			
		||||
   * @returns {object} {libraryItems:LibraryItem[], count:number}
 | 
			
		||||
   */
 | 
			
		||||
  async getFilteredPodcastEpisodes(libraryId, userId, filterGroup, filterValue, sortBy, sortDesc, limit, offset) {
 | 
			
		||||
    if (sortBy === 'progress' && filterGroup !== 'progress') {
 | 
			
		||||
      Logger.warn('Cannot sort podcast episodes by progress without filtering by progress')
 | 
			
		||||
      sortBy = 'createdAt'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const podcastEpisodeIncludes = []
 | 
			
		||||
    let podcastEpisodeWhere = {}
 | 
			
		||||
    if (filterGroup === 'progress') {
 | 
			
		||||
      podcastEpisodeIncludes.push({
 | 
			
		||||
        model: Database.models.mediaProgress,
 | 
			
		||||
        where: {
 | 
			
		||||
          userId
 | 
			
		||||
        },
 | 
			
		||||
        attributes: ['id', 'isFinished', 'currentTime', 'updatedAt']
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      if (filterValue === 'in-progress') {
 | 
			
		||||
        podcastEpisodeWhere = [
 | 
			
		||||
          {
 | 
			
		||||
            '$mediaProgresses.isFinished$': false
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            '$mediaProgresses.currentTime$': {
 | 
			
		||||
              [Sequelize.Op.gt]: 0
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      } else if (filterValue === 'finished') {
 | 
			
		||||
        podcastEpisodeWhere['$mediaProgresses.isFinished$'] = true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const podcastEpisodeOrder = []
 | 
			
		||||
    if (sortBy === 'createdAt') {
 | 
			
		||||
      podcastEpisodeOrder.push(['createdAt', sortDesc ? 'DESC' : 'ASC'])
 | 
			
		||||
    } else if (sortBy === 'progress') {
 | 
			
		||||
      podcastEpisodeOrder.push([Sequelize.literal('mediaProgresses.updatedAt'), sortDesc ? 'DESC' : 'ASC'])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { rows: podcastEpisodes, count } = await Database.models.podcastEpisode.findAndCountAll({
 | 
			
		||||
      where: podcastEpisodeWhere,
 | 
			
		||||
      include: [
 | 
			
		||||
        {
 | 
			
		||||
          model: Database.models.podcast,
 | 
			
		||||
          include: [
 | 
			
		||||
            {
 | 
			
		||||
              model: Database.models.libraryItem,
 | 
			
		||||
              where: {
 | 
			
		||||
                libraryId
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        },
 | 
			
		||||
        ...podcastEpisodeIncludes
 | 
			
		||||
      ],
 | 
			
		||||
      distinct: true,
 | 
			
		||||
      subQuery: false,
 | 
			
		||||
      order: podcastEpisodeOrder,
 | 
			
		||||
      limit,
 | 
			
		||||
      offset
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    const libraryItems = podcastEpisodes.map((ep) => {
 | 
			
		||||
      const libraryItem = ep.podcast.libraryItem.toJSON()
 | 
			
		||||
      const podcast = ep.podcast.toJSON()
 | 
			
		||||
      delete podcast.libraryItem
 | 
			
		||||
      libraryItem.media = podcast
 | 
			
		||||
      libraryItem.recentEpisode = ep.getOldPodcastEpisode(libraryItem.id)
 | 
			
		||||
      return libraryItem
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      libraryItems,
 | 
			
		||||
      count
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user