mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-04 18:24:46 +02:00
Merge
This commit is contained in:
commit
8b685436de
55 changed files with 641 additions and 236 deletions
57
server/Db.js
57
server/Db.js
|
@ -27,17 +27,16 @@ class Db {
|
|||
this.SeriesPath = Path.join(global.ConfigPath, 'series')
|
||||
this.FeedsPath = Path.join(global.ConfigPath, 'feeds')
|
||||
|
||||
const staleTime = 1000 * 60 * 2
|
||||
this.libraryItemsDb = new njodb.Database(this.LibraryItemsPath, { lockoptions: { stale: staleTime } })
|
||||
this.usersDb = new njodb.Database(this.UsersPath, { lockoptions: { stale: staleTime } })
|
||||
this.sessionsDb = new njodb.Database(this.SessionsPath, { lockoptions: { stale: staleTime } })
|
||||
this.librariesDb = new njodb.Database(this.LibrariesPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.settingsDb = new njodb.Database(this.SettingsPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.collectionsDb = new njodb.Database(this.CollectionsPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.playlistsDb = new njodb.Database(this.PlaylistsPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.authorsDb = new njodb.Database(this.AuthorsPath, { lockoptions: { stale: staleTime } })
|
||||
this.seriesDb = new njodb.Database(this.SeriesPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.feedsDb = new njodb.Database(this.FeedsPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.libraryItemsDb = new njodb.Database(this.LibraryItemsPath, this.getNjodbOptions())
|
||||
this.usersDb = new njodb.Database(this.UsersPath, this.getNjodbOptions())
|
||||
this.sessionsDb = new njodb.Database(this.SessionsPath, this.getNjodbOptions())
|
||||
this.librariesDb = new njodb.Database(this.LibrariesPath, this.getNjodbOptions())
|
||||
this.settingsDb = new njodb.Database(this.SettingsPath, this.getNjodbOptions())
|
||||
this.collectionsDb = new njodb.Database(this.CollectionsPath, this.getNjodbOptions())
|
||||
this.playlistsDb = new njodb.Database(this.PlaylistsPath, this.getNjodbOptions())
|
||||
this.authorsDb = new njodb.Database(this.AuthorsPath, this.getNjodbOptions())
|
||||
this.seriesDb = new njodb.Database(this.SeriesPath, this.getNjodbOptions())
|
||||
this.feedsDb = new njodb.Database(this.FeedsPath, this.getNjodbOptions())
|
||||
|
||||
this.libraryItems = []
|
||||
this.users = []
|
||||
|
@ -59,6 +58,21 @@ class Db {
|
|||
return this.users.some(u => u.id === 'root')
|
||||
}
|
||||
|
||||
getNjodbOptions() {
|
||||
return {
|
||||
lockoptions: {
|
||||
stale: 1000 * 20, // 20 seconds
|
||||
update: 2500,
|
||||
retries: {
|
||||
retries: 20,
|
||||
minTimeout: 250,
|
||||
maxTimeout: 5000,
|
||||
factor: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getEntityDb(entityName) {
|
||||
if (entityName === 'user') return this.usersDb
|
||||
else if (entityName === 'session') return this.sessionsDb
|
||||
|
@ -88,17 +102,16 @@ class Db {
|
|||
}
|
||||
|
||||
reinit() {
|
||||
const staleTime = 1000 * 60 * 2
|
||||
this.libraryItemsDb = new njodb.Database(this.LibraryItemsPath, { lockoptions: { stale: staleTime } })
|
||||
this.usersDb = new njodb.Database(this.UsersPath, { lockoptions: { stale: staleTime } })
|
||||
this.sessionsDb = new njodb.Database(this.SessionsPath, { lockoptions: { stale: staleTime } })
|
||||
this.librariesDb = new njodb.Database(this.LibrariesPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.settingsDb = new njodb.Database(this.SettingsPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.collectionsDb = new njodb.Database(this.CollectionsPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.playlistsDb = new njodb.Database(this.PlaylistsPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.authorsDb = new njodb.Database(this.AuthorsPath, { lockoptions: { stale: staleTime } })
|
||||
this.seriesDb = new njodb.Database(this.SeriesPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.feedsDb = new njodb.Database(this.FeedsPath, { datastores: 2, lockoptions: { stale: staleTime } })
|
||||
this.libraryItemsDb = new njodb.Database(this.LibraryItemsPath, this.getNjodbOptions())
|
||||
this.usersDb = new njodb.Database(this.UsersPath, this.getNjodbOptions())
|
||||
this.sessionsDb = new njodb.Database(this.SessionsPath, this.getNjodbOptions())
|
||||
this.librariesDb = new njodb.Database(this.LibrariesPath, this.getNjodbOptions())
|
||||
this.settingsDb = new njodb.Database(this.SettingsPath, this.getNjodbOptions())
|
||||
this.collectionsDb = new njodb.Database(this.CollectionsPath, this.getNjodbOptions())
|
||||
this.playlistsDb = new njodb.Database(this.PlaylistsPath, this.getNjodbOptions())
|
||||
this.authorsDb = new njodb.Database(this.AuthorsPath, this.getNjodbOptions())
|
||||
this.seriesDb = new njodb.Database(this.SeriesPath, this.getNjodbOptions())
|
||||
this.feedsDb = new njodb.Database(this.FeedsPath, this.getNjodbOptions())
|
||||
return this.init()
|
||||
}
|
||||
|
||||
|
|
|
@ -167,18 +167,19 @@ class AuthorController {
|
|||
}
|
||||
|
||||
async match(req, res) {
|
||||
var authorData = null
|
||||
let authorData = null
|
||||
const region = req.body.region || 'us'
|
||||
if (req.body.asin) {
|
||||
authorData = await this.authorFinder.findAuthorByASIN(req.body.asin)
|
||||
authorData = await this.authorFinder.findAuthorByASIN(req.body.asin, region)
|
||||
} else {
|
||||
authorData = await this.authorFinder.findAuthorByName(req.body.q)
|
||||
authorData = await this.authorFinder.findAuthorByName(req.body.q, region)
|
||||
}
|
||||
if (!authorData) {
|
||||
return res.status(404).send('Author not found')
|
||||
}
|
||||
Logger.debug(`[AuthorController] match author with "${req.body.q || req.body.asin}"`, authorData)
|
||||
|
||||
var hasUpdates = false
|
||||
let hasUpdates = false
|
||||
if (authorData.asin && req.author.asin !== authorData.asin) {
|
||||
req.author.asin = authorData.asin
|
||||
hasUpdates = true
|
||||
|
@ -188,7 +189,7 @@ class AuthorController {
|
|||
if (authorData.image && (!req.author.imagePath || hasUpdates)) {
|
||||
this.cacheManager.purgeImageCache(req.author.id)
|
||||
|
||||
var imageData = await this.authorFinder.saveAuthorImage(req.author.id, authorData.image)
|
||||
const imageData = await this.authorFinder.saveAuthorImage(req.author.id, authorData.image)
|
||||
if (imageData) {
|
||||
req.author.imagePath = imageData.path
|
||||
hasUpdates = true
|
||||
|
@ -204,7 +205,7 @@ class AuthorController {
|
|||
req.author.updatedAt = Date.now()
|
||||
|
||||
await this.db.updateEntity('author', req.author)
|
||||
var numBooks = this.db.libraryItems.filter(li => {
|
||||
const numBooks = this.db.libraryItems.filter(li => {
|
||||
return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(req.author.id)
|
||||
}).length
|
||||
SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks))
|
||||
|
|
|
@ -596,6 +596,7 @@ class LibraryController {
|
|||
|
||||
const itemMatches = []
|
||||
const authorMatches = {}
|
||||
const narratorMatches = {}
|
||||
const seriesMatches = {}
|
||||
const tagMatches = {}
|
||||
|
||||
|
@ -608,7 +609,7 @@ class LibraryController {
|
|||
matchText: queryResult.matchText
|
||||
})
|
||||
}
|
||||
if (queryResult.series && queryResult.series.length) {
|
||||
if (queryResult.series?.length) {
|
||||
queryResult.series.forEach((se) => {
|
||||
if (!seriesMatches[se.id]) {
|
||||
const _series = this.db.series.find(_se => _se.id === se.id)
|
||||
|
@ -618,7 +619,7 @@ class LibraryController {
|
|||
}
|
||||
})
|
||||
}
|
||||
if (queryResult.authors && queryResult.authors.length) {
|
||||
if (queryResult.authors?.length) {
|
||||
queryResult.authors.forEach((au) => {
|
||||
if (!authorMatches[au.id]) {
|
||||
const _author = this.db.authors.find(_au => _au.id === au.id)
|
||||
|
@ -631,7 +632,7 @@ class LibraryController {
|
|||
}
|
||||
})
|
||||
}
|
||||
if (queryResult.tags && queryResult.tags.length) {
|
||||
if (queryResult.tags?.length) {
|
||||
queryResult.tags.forEach((tag) => {
|
||||
if (!tagMatches[tag]) {
|
||||
tagMatches[tag] = { name: tag, books: [li.toJSON()] }
|
||||
|
@ -640,13 +641,23 @@ class LibraryController {
|
|||
}
|
||||
})
|
||||
}
|
||||
if (queryResult.narrators?.length) {
|
||||
queryResult.narrators.forEach((narrator) => {
|
||||
if (!narratorMatches[narrator]) {
|
||||
narratorMatches[narrator] = { name: narrator, books: [li.toJSON()] }
|
||||
} else {
|
||||
narratorMatches[narrator].books.push(li.toJSON())
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
const itemKey = req.library.mediaType
|
||||
const results = {
|
||||
[itemKey]: itemMatches.slice(0, maxResults),
|
||||
tags: Object.values(tagMatches).slice(0, maxResults),
|
||||
authors: Object.values(authorMatches).slice(0, maxResults),
|
||||
series: Object.values(seriesMatches).slice(0, maxResults)
|
||||
series: Object.values(seriesMatches).slice(0, maxResults),
|
||||
narrators: Object.values(narratorMatches).slice(0, maxResults)
|
||||
}
|
||||
res.json(results)
|
||||
}
|
||||
|
|
|
@ -20,16 +20,16 @@ class AuthorFinder {
|
|||
})
|
||||
}
|
||||
|
||||
findAuthorByASIN(asin) {
|
||||
findAuthorByASIN(asin, region) {
|
||||
if (!asin) return null
|
||||
return this.audnexus.findAuthorByASIN(asin)
|
||||
return this.audnexus.findAuthorByASIN(asin, region)
|
||||
}
|
||||
|
||||
async findAuthorByName(name, options = {}) {
|
||||
async findAuthorByName(name, region, options = {}) {
|
||||
if (!name) return null
|
||||
const maxLevenshtein = !isNaN(options.maxLevenshtein) ? Number(options.maxLevenshtein) : 3
|
||||
|
||||
var author = await this.audnexus.findAuthorByName(name, maxLevenshtein)
|
||||
const author = await this.audnexus.findAuthorByName(name, region, maxLevenshtein)
|
||||
if (!author || !author.name) {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ const Audible = require('../providers/Audible')
|
|||
const iTunes = require('../providers/iTunes')
|
||||
const Audnexus = require('../providers/Audnexus')
|
||||
const FantLab = require('../providers/FantLab')
|
||||
const AudiobookCovers = require('../providers/AudiobookCovers')
|
||||
const Logger = require('../Logger')
|
||||
const { levenshteinDistance } = require('../utils/index')
|
||||
|
||||
|
@ -15,6 +16,7 @@ class BookFinder {
|
|||
this.iTunesApi = new iTunes()
|
||||
this.audnexus = new Audnexus()
|
||||
this.fantLab = new FantLab()
|
||||
this.audiobookCovers = new AudiobookCovers()
|
||||
|
||||
this.verbose = false
|
||||
}
|
||||
|
@ -159,6 +161,12 @@ class BookFinder {
|
|||
return books
|
||||
}
|
||||
|
||||
async getAudiobookCoversResults(search) {
|
||||
const covers = await this.audiobookCovers.search(search)
|
||||
if (this.verbose) Logger.debug(`AudiobookCovers Search Results: ${covers.length || 0}`)
|
||||
return covers || []
|
||||
}
|
||||
|
||||
async getiTunesAudiobooksResults(title, author) {
|
||||
return this.iTunesApi.searchAudiobooks(title)
|
||||
}
|
||||
|
@ -187,6 +195,8 @@ class BookFinder {
|
|||
books = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance)
|
||||
} else if (provider === 'fantlab') {
|
||||
books = await this.getFantLabResults(title, author)
|
||||
} else if (provider === 'audiobookcovers') {
|
||||
books = await this.getAudiobookCoversResults(title)
|
||||
}
|
||||
else {
|
||||
books = await this.getGoogleBooksResults(title, author)
|
||||
|
@ -202,11 +212,13 @@ class BookFinder {
|
|||
return this.search(provider, cleanedTitle, cleanedAuthor, isbn, asin, options)
|
||||
}
|
||||
|
||||
if (["google", "audible", "itunes", 'fantlab'].includes(provider)) return books
|
||||
if (provider === 'openlibrary') {
|
||||
books.sort((a, b) => {
|
||||
return a.totalDistance - b.totalDistance
|
||||
})
|
||||
}
|
||||
|
||||
return books.sort((a, b) => {
|
||||
return a.totalDistance - b.totalDistance
|
||||
})
|
||||
return books
|
||||
}
|
||||
|
||||
async findCovers(provider, title, author, options = {}) {
|
||||
|
|
|
@ -118,6 +118,7 @@ function updateLock(file, options) {
|
|||
// the lockfile was deleted or we are over the threshold
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT' || isOverThreshold) {
|
||||
console.error(`lockfile "${file}" compromised. stat code=${err.code}, isOverThreshold=${isOverThreshold}`)
|
||||
return setLockAsCompromised(file, lock, Object.assign(err, { code: 'ECOMPROMISED' }));
|
||||
}
|
||||
|
||||
|
@ -129,6 +130,7 @@ function updateLock(file, options) {
|
|||
const isMtimeOurs = lock.mtime.getTime() === stat.mtime.getTime();
|
||||
|
||||
if (!isMtimeOurs) {
|
||||
console.error(`lockfile "${file}" compromised. mtime is not ours`)
|
||||
return setLockAsCompromised(
|
||||
file,
|
||||
lock,
|
||||
|
@ -152,6 +154,7 @@ function updateLock(file, options) {
|
|||
// the lockfile was deleted or we are over the threshold
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT' || isOverThreshold) {
|
||||
console.error(`lockfile "${file}" compromised. utimes code=${err.code}, isOverThreshold=${isOverThreshold}`)
|
||||
return setLockAsCompromised(file, lock, Object.assign(err, { code: 'ECOMPROMISED' }));
|
||||
}
|
||||
|
||||
|
|
|
@ -322,6 +322,7 @@ class Book {
|
|||
tags: this.tags.filter(t => cleanStringForSearch(t).includes(query)),
|
||||
series: this.metadata.searchSeries(query),
|
||||
authors: this.metadata.searchAuthors(query),
|
||||
narrators: this.metadata.searchNarrators(query),
|
||||
matchKey: null,
|
||||
matchText: null
|
||||
}
|
||||
|
@ -336,10 +337,12 @@ class Book {
|
|||
} else if (payload.series.length) {
|
||||
payload.matchKey = 'series'
|
||||
payload.matchText = this.metadata.seriesName
|
||||
}
|
||||
else if (payload.tags.length) {
|
||||
} else if (payload.tags.length) {
|
||||
payload.matchKey = 'tags'
|
||||
payload.matchText = this.tags.join(', ')
|
||||
} else if (payload.narrators.length) {
|
||||
payload.matchKey = 'narrators'
|
||||
payload.matchText = this.metadata.narratorName
|
||||
}
|
||||
}
|
||||
return payload
|
||||
|
|
|
@ -381,6 +381,9 @@ class BookMetadata {
|
|||
searchAuthors(query) {
|
||||
return this.authors.filter(au => cleanStringForSearch(au.name).includes(query))
|
||||
}
|
||||
searchNarrators(query) {
|
||||
return this.narrators.filter(n => cleanStringForSearch(n).includes(query))
|
||||
}
|
||||
searchQuery(query) { // Returns key if match is found
|
||||
const keysToCheck = ['title', 'asin', 'isbn']
|
||||
for (const key of keysToCheck) {
|
||||
|
|
|
@ -20,7 +20,7 @@ class User {
|
|||
|
||||
this.permissions = {}
|
||||
this.librariesAccessible = [] // Library IDs (Empty if ALL libraries)
|
||||
this.itemTagsAccessible = [] // Empty if ALL item tags accessible
|
||||
this.itemTagsSelected = [] // Empty if ALL item tags accessible
|
||||
|
||||
if (user) {
|
||||
this.construct(user)
|
||||
|
@ -86,7 +86,7 @@ class User {
|
|||
createdAt: this.createdAt,
|
||||
permissions: this.permissions,
|
||||
librariesAccessible: [...this.librariesAccessible],
|
||||
itemTagsAccessible: [...this.itemTagsAccessible]
|
||||
itemTagsSelected: [...this.itemTagsSelected]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,7 @@ class User {
|
|||
createdAt: this.createdAt,
|
||||
permissions: this.permissions,
|
||||
librariesAccessible: [...this.librariesAccessible],
|
||||
itemTagsAccessible: [...this.itemTagsAccessible]
|
||||
itemTagsSelected: [...this.itemTagsSelected]
|
||||
}
|
||||
if (minimal) {
|
||||
delete json.mediaProgress
|
||||
|
@ -169,9 +169,14 @@ class User {
|
|||
if (this.permissions.accessAllTags === undefined) this.permissions.accessAllTags = true
|
||||
// Explicit content restriction permission added v2.0.18
|
||||
if (this.permissions.accessExplicitContent === undefined) this.permissions.accessExplicitContent = true
|
||||
// itemTagsAccessible was renamed to itemTagsSelected in version v2.2.20
|
||||
if (user.itemTagsAccessible?.length) {
|
||||
this.permissions.selectedTagsNotAccessible = false
|
||||
user.itemTagsSelected = user.itemTagsAccessible
|
||||
}
|
||||
|
||||
this.librariesAccessible = [...(user.librariesAccessible || [])]
|
||||
this.itemTagsAccessible = [...(user.itemTagsAccessible || [])]
|
||||
this.itemTagsSelected = [...(user.itemTagsSelected || [])]
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
|
@ -228,19 +233,21 @@ class User {
|
|||
// Update accessible tags
|
||||
if (this.permissions.accessAllTags) {
|
||||
// Access all tags
|
||||
if (this.itemTagsAccessible.length) {
|
||||
this.itemTagsAccessible = []
|
||||
if (this.itemTagsSelected.length) {
|
||||
this.itemTagsSelected = []
|
||||
this.permissions.selectedTagsNotAccessible = false
|
||||
hasUpdates = true
|
||||
}
|
||||
} else if (payload.itemTagsAccessible !== undefined) {
|
||||
if (payload.itemTagsAccessible.length) {
|
||||
if (payload.itemTagsAccessible.join(',') !== this.itemTagsAccessible.join(',')) {
|
||||
} else if (payload.itemTagsSelected !== undefined) {
|
||||
if (payload.itemTagsSelected.length) {
|
||||
if (payload.itemTagsSelected.join(',') !== this.itemTagsSelected.join(',')) {
|
||||
hasUpdates = true
|
||||
this.itemTagsAccessible = [...payload.itemTagsAccessible]
|
||||
this.itemTagsSelected = [...payload.itemTagsSelected]
|
||||
}
|
||||
} else if (this.itemTagsAccessible.length > 0) {
|
||||
} else if (this.itemTagsSelected.length > 0) {
|
||||
hasUpdates = true
|
||||
this.itemTagsAccessible = []
|
||||
this.itemTagsSelected = []
|
||||
this.permissions.selectedTagsNotAccessible = false
|
||||
}
|
||||
}
|
||||
return hasUpdates
|
||||
|
@ -343,8 +350,12 @@ class User {
|
|||
|
||||
checkCanAccessLibraryItemWithTags(tags) {
|
||||
if (this.permissions.accessAllTags) return true
|
||||
if (!tags || !tags.length) return false
|
||||
return this.itemTagsAccessible.some(tag => tags.includes(tag))
|
||||
if (this.permissions.selectedTagsNotAccessible) {
|
||||
if (!tags?.length) return true
|
||||
return tags.every(tag => !this.itemTagsSelected.includes(tag))
|
||||
}
|
||||
if (!tags?.length) return false
|
||||
return this.itemTagsSelected.some(tag => tags.includes(tag))
|
||||
}
|
||||
|
||||
checkCanAccessLibraryItem(libraryItem) {
|
||||
|
|
23
server/providers/AudiobookCovers.js
Normal file
23
server/providers/AudiobookCovers.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
const axios = require('axios')
|
||||
const Logger = require('../Logger')
|
||||
|
||||
class AudiobookCovers {
|
||||
constructor() { }
|
||||
|
||||
async search(search) {
|
||||
const url = `https://api.audiobookcovers.com/cover/bytext/`
|
||||
const params = new URLSearchParams([['q', search]])
|
||||
const items = await axios.get(url, { params }).then((res) => {
|
||||
if (!res || !res.data) return []
|
||||
return res.data
|
||||
}).catch(error => {
|
||||
Logger.error('[AudiobookCovers] Cover search error', error)
|
||||
return []
|
||||
})
|
||||
return items.map(item => ({ cover: item.filename }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = AudiobookCovers
|
|
@ -7,9 +7,12 @@ class Audnexus {
|
|||
this.baseUrl = 'https://api.audnex.us'
|
||||
}
|
||||
|
||||
authorASINsRequest(name) {
|
||||
name = encodeURIComponent(name);
|
||||
return axios.get(`${this.baseUrl}/authors?name=${name}`).then((res) => {
|
||||
authorASINsRequest(name, region) {
|
||||
name = encodeURIComponent(name)
|
||||
const regionQuery = region ? `®ion=${region}` : ''
|
||||
const authorRequestUrl = `${this.baseUrl}/authors?name=${name}${regionQuery}`
|
||||
Logger.info(`[Audnexus] Searching for author "${authorRequestUrl}"`)
|
||||
return axios.get(authorRequestUrl).then((res) => {
|
||||
return res.data || []
|
||||
}).catch((error) => {
|
||||
Logger.error(`[Audnexus] Author ASIN request failed for ${name}`, error)
|
||||
|
@ -17,9 +20,12 @@ class Audnexus {
|
|||
})
|
||||
}
|
||||
|
||||
authorRequest(asin) {
|
||||
asin = encodeURIComponent(asin);
|
||||
return axios.get(`${this.baseUrl}/authors/${asin}`).then((res) => {
|
||||
authorRequest(asin, region) {
|
||||
asin = encodeURIComponent(asin)
|
||||
const regionQuery = region ? `?region=${region}` : ''
|
||||
const authorRequestUrl = `${this.baseUrl}/authors/${asin}${regionQuery}`
|
||||
Logger.info(`[Audnexus] Searching for author "${authorRequestUrl}"`)
|
||||
return axios.get(authorRequestUrl).then((res) => {
|
||||
return res.data
|
||||
}).catch((error) => {
|
||||
Logger.error(`[Audnexus] Author request failed for ${asin}`, error)
|
||||
|
@ -27,8 +33,8 @@ class Audnexus {
|
|||
})
|
||||
}
|
||||
|
||||
async findAuthorByASIN(asin) {
|
||||
var author = await this.authorRequest(asin)
|
||||
async findAuthorByASIN(asin, region) {
|
||||
const author = await this.authorRequest(asin, region)
|
||||
if (!author) {
|
||||
return null
|
||||
}
|
||||
|
@ -40,14 +46,14 @@ class Audnexus {
|
|||
}
|
||||
}
|
||||
|
||||
async findAuthorByName(name, maxLevenshtein = 3) {
|
||||
async findAuthorByName(name, region, maxLevenshtein = 3) {
|
||||
Logger.debug(`[Audnexus] Looking up author by name ${name}`)
|
||||
var asins = await this.authorASINsRequest(name)
|
||||
var matchingAsin = asins.find(obj => levenshteinDistance(obj.name, name) <= maxLevenshtein)
|
||||
const asins = await this.authorASINsRequest(name, region)
|
||||
const matchingAsin = asins.find(obj => levenshteinDistance(obj.name, name) <= maxLevenshtein)
|
||||
if (!matchingAsin) {
|
||||
return null
|
||||
}
|
||||
var author = await this.authorRequest(matchingAsin.asin)
|
||||
const author = await this.authorRequest(matchingAsin.asin)
|
||||
if (!author) {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -410,6 +410,12 @@ class ApiRouter {
|
|||
await this.cacheManager.purgeCoverCache(libraryItem.id)
|
||||
}
|
||||
|
||||
const itemMetadataPath = Path.join(global.MetadataPath, 'items', libraryItem.id)
|
||||
if (await fs.pathExists(itemMetadataPath)) {
|
||||
Logger.debug(`[ApiRouter] Removing item metadata path "${itemMetadataPath}"`)
|
||||
await fs.remove(itemMetadataPath)
|
||||
}
|
||||
|
||||
await this.db.removeLibraryItem(libraryItem.id)
|
||||
SocketAuthority.emitter('item_removed', libraryItem.toJSONExpanded())
|
||||
}
|
||||
|
|
|
@ -25,6 +25,11 @@ module.exports = function areEquivalent(value1, value2, stack = []) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Truthy check to handle value1=null, value2=Object
|
||||
if ((value1 && !value2) || (!value1 && value2)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const type1 = typeof value1;
|
||||
|
||||
// Ensure types match
|
||||
|
|
|
@ -92,7 +92,6 @@ module.exports.setDefault = (path, silent = false) => {
|
|||
const gid = global.Gid
|
||||
return new Promise((resolve) => {
|
||||
if (isNaN(uid) || isNaN(gid)) {
|
||||
if (!silent) Logger.debug('Not modifying permissions since no uid/gid is specified')
|
||||
return resolve()
|
||||
}
|
||||
if (!silent) Logger.debug(`Setting permission "${mode}" for uid ${uid} and gid ${gid} | "${path}"`)
|
||||
|
|
|
@ -174,20 +174,24 @@ async function recurseFiles(path, relPathToReplace = null) {
|
|||
}
|
||||
module.exports.recurseFiles = recurseFiles
|
||||
|
||||
module.exports.downloadFile = async (url, filepath) => {
|
||||
Logger.debug(`[fileUtils] Downloading file to ${filepath}`)
|
||||
module.exports.downloadFile = (url, filepath) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
Logger.debug(`[fileUtils] Downloading file to ${filepath}`)
|
||||
axios({
|
||||
url,
|
||||
method: 'GET',
|
||||
responseType: 'stream',
|
||||
timeout: 30000
|
||||
}).then((response) => {
|
||||
const writer = fs.createWriteStream(filepath)
|
||||
response.data.pipe(writer)
|
||||
|
||||
const writer = fs.createWriteStream(filepath)
|
||||
const response = await axios({
|
||||
url,
|
||||
method: 'GET',
|
||||
responseType: 'stream',
|
||||
timeout: 30000
|
||||
})
|
||||
response.data.pipe(writer)
|
||||
return new Promise((resolve, reject) => {
|
||||
writer.on('finish', resolve)
|
||||
writer.on('error', reject)
|
||||
writer.on('finish', resolve)
|
||||
writer.on('error', reject)
|
||||
}).catch((err) => {
|
||||
Logger.error(`[fileUtils] Failed to download file "${filepath}"`, err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -85,6 +85,8 @@ function extractEpisodeData(item) {
|
|||
}
|
||||
}
|
||||
|
||||
episode.enclosure.url = episode.enclosure.url.trim()
|
||||
|
||||
// Full description with html
|
||||
if (item['content:encoded']) {
|
||||
const rawDescription = (extractFirstArrayItem(item, 'content:encoded') || '').trim()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue