mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
commit
5d40fdf277
@ -52,6 +52,10 @@ export const state = () => ({
|
|||||||
{
|
{
|
||||||
text: 'Audible.es',
|
text: 'Audible.es',
|
||||||
value: 'audible.es'
|
value: 'audible.es'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'FantLab.ru',
|
||||||
|
value: 'fantlab'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
podcastProviders: [
|
podcastProviders: [
|
||||||
|
@ -3,6 +3,7 @@ const GoogleBooks = require('../providers/GoogleBooks')
|
|||||||
const Audible = require('../providers/Audible')
|
const Audible = require('../providers/Audible')
|
||||||
const iTunes = require('../providers/iTunes')
|
const iTunes = require('../providers/iTunes')
|
||||||
const Audnexus = require('../providers/Audnexus')
|
const Audnexus = require('../providers/Audnexus')
|
||||||
|
const FantLab = require('../providers/FantLab')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const { levenshteinDistance } = require('../utils/index')
|
const { levenshteinDistance } = require('../utils/index')
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ class BookFinder {
|
|||||||
this.audible = new Audible()
|
this.audible = new Audible()
|
||||||
this.iTunesApi = new iTunes()
|
this.iTunesApi = new iTunes()
|
||||||
this.audnexus = new Audnexus()
|
this.audnexus = new Audnexus()
|
||||||
|
this.fantLab = new FantLab()
|
||||||
|
|
||||||
this.verbose = false
|
this.verbose = false
|
||||||
}
|
}
|
||||||
@ -146,6 +148,17 @@ class BookFinder {
|
|||||||
return books
|
return books
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getFantLabResults(title, author) {
|
||||||
|
var books = await this.fantLab.search(title, author)
|
||||||
|
if (this.verbose) Logger.debug(`FantLab Book Search Results: ${books.length || 0}`)
|
||||||
|
if (books.errorCode) {
|
||||||
|
Logger.error(`FantLab Search Error ${books.errorCode}`)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return books
|
||||||
|
}
|
||||||
|
|
||||||
async getiTunesAudiobooksResults(title, author) {
|
async getiTunesAudiobooksResults(title, author) {
|
||||||
return this.iTunesApi.searchAudiobooks(title)
|
return this.iTunesApi.searchAudiobooks(title)
|
||||||
}
|
}
|
||||||
@ -172,7 +185,10 @@ class BookFinder {
|
|||||||
books = await this.getiTunesAudiobooksResults(title, author)
|
books = await this.getiTunesAudiobooksResults(title, author)
|
||||||
} else if (provider === 'openlibrary') {
|
} else if (provider === 'openlibrary') {
|
||||||
books = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance)
|
books = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance)
|
||||||
} else {
|
} else if (provider === 'fantlab') {
|
||||||
|
books = await this.getFantLabResults(title, author)
|
||||||
|
}
|
||||||
|
else {
|
||||||
books = await this.getGoogleBooksResults(title, author)
|
books = await this.getGoogleBooksResults(title, author)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +202,7 @@ class BookFinder {
|
|||||||
return this.search(provider, cleanedTitle, cleanedAuthor, isbn, asin, options)
|
return this.search(provider, cleanedTitle, cleanedAuthor, isbn, asin, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (["google", "audible", "itunes"].includes(provider)) return books
|
if (["google", "audible", "itunes", 'fantlab'].includes(provider)) return books
|
||||||
|
|
||||||
return books.sort((a, b) => {
|
return books.sort((a, b) => {
|
||||||
return a.totalDistance - b.totalDistance
|
return a.totalDistance - b.totalDistance
|
||||||
|
152
server/providers/FantLab.js
Normal file
152
server/providers/FantLab.js
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
const axios = require('axios')
|
||||||
|
const Logger = require('../Logger')
|
||||||
|
|
||||||
|
class FantLab {
|
||||||
|
// 7 - other
|
||||||
|
// 11 - essay
|
||||||
|
// 12 - article
|
||||||
|
// 22 - disser
|
||||||
|
// 23 - monography
|
||||||
|
// 24 - study
|
||||||
|
// 25 - encyclopedy
|
||||||
|
// 26 - magazine
|
||||||
|
// 46 - sketch
|
||||||
|
// 47 - reportage
|
||||||
|
// 49 - excerpt
|
||||||
|
// 51 - interview
|
||||||
|
// 52 - review
|
||||||
|
// 55 - libretto
|
||||||
|
// 56 - anthology series
|
||||||
|
// 57 - newspaper
|
||||||
|
// types can get here https://api.fantlab.ru/config.json
|
||||||
|
_filterWorkType = [7, 11, 12, 22, 23, 24, 25, 26, 46, 47, 49, 51, 52, 55, 56, 57]
|
||||||
|
_baseUrl = 'https://api.fantlab.ru'
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
async search(title, author) {
|
||||||
|
let searchString = encodeURIComponent(title)
|
||||||
|
if (author) {
|
||||||
|
searchString += encodeURIComponent(' ' + author)
|
||||||
|
}
|
||||||
|
const url = `${this._baseUrl}/search-works?q=${searchString}&page=1&onlymatches=1`
|
||||||
|
Logger.debug(`[FantLab] Search url: ${url}`)
|
||||||
|
const items = await axios.get(url).then((res) => {
|
||||||
|
return res.data || []
|
||||||
|
}).catch(error => {
|
||||||
|
Logger.error('[FantLab] search error', error)
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.all(items.map(async item => await this.getWork(item))).then(resArray => {
|
||||||
|
return resArray.filter(res => res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWork(item) {
|
||||||
|
const { work_id, work_type_id } = item
|
||||||
|
|
||||||
|
if (this._filterWorkType.includes(work_type_id)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${this._baseUrl}/work/${work_id}/extended`
|
||||||
|
const bookData = await axios.get(url).then((resp) => {
|
||||||
|
return resp.data || null
|
||||||
|
}).catch((error) => {
|
||||||
|
Logger.error(`[FantLab] work info request for url "${url}" error`, error)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.cleanBookData(bookData)
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanBookData(bookData) {
|
||||||
|
let { authors, work_name_alts, work_id, work_name, work_year, work_description, image, classificatory, editions_blocks } = bookData
|
||||||
|
|
||||||
|
const subtitle = Array.isArray(work_name_alts) ? work_name_alts[0] : null
|
||||||
|
const authorNames = authors.map(au => (au.name || '').trim()).filter(au => au)
|
||||||
|
|
||||||
|
const imageAndIsbn = await this.tryGetCoverFromEditions(editions_blocks)
|
||||||
|
|
||||||
|
const imageToUse = imageAndIsbn?.imageUrl || image
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: work_id,
|
||||||
|
title: work_name,
|
||||||
|
subtitle: subtitle || null,
|
||||||
|
author: authorNames.length ? authorNames.join(', ') : null,
|
||||||
|
publisher: null,
|
||||||
|
publishedYear: work_year,
|
||||||
|
description: work_description,
|
||||||
|
cover: imageToUse ? `https://fantlab.ru${imageToUse}` : null,
|
||||||
|
genres: this.tryGetGenres(classificatory),
|
||||||
|
isbn: imageAndIsbn?.isbn || null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tryGetGenres(classificatory) {
|
||||||
|
if (!classificatory || !classificatory.genre_group) return []
|
||||||
|
|
||||||
|
const genresGroup = classificatory.genre_group.find(group => group.genre_group_id == 1) // genres and subgenres
|
||||||
|
|
||||||
|
// genre_group_id=2 - General Characteristics
|
||||||
|
// genre_group_id=3 - Arena
|
||||||
|
// genre_group_id=4 - Duration of action
|
||||||
|
// genre_group_id=6 - Story moves
|
||||||
|
// genre_group_id=7 - Story linearity
|
||||||
|
// genre_group_id=5 - Recommended age of the reader
|
||||||
|
|
||||||
|
if (!genresGroup || !genresGroup.genre || !genresGroup.genre.length) return []
|
||||||
|
|
||||||
|
const rootGenre = genresGroup.genre[0]
|
||||||
|
|
||||||
|
const { label } = rootGenre
|
||||||
|
|
||||||
|
return [label].concat(this.tryGetSubGenres(rootGenre))
|
||||||
|
}
|
||||||
|
|
||||||
|
tryGetSubGenres(rootGenre) {
|
||||||
|
if (!rootGenre.genre || !rootGenre.genre.length) return []
|
||||||
|
return rootGenre.genre.map(g => g.label).filter(g => g)
|
||||||
|
}
|
||||||
|
|
||||||
|
async tryGetCoverFromEditions(editions) {
|
||||||
|
if (!editions) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 30 = audio, 10 = paper
|
||||||
|
// Prefer audio if available
|
||||||
|
const bookEditions = editions['30'] || editions['10']
|
||||||
|
if (!bookEditions || !bookEditions.list || !bookEditions.list.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastEdition = bookEditions.list.pop()
|
||||||
|
|
||||||
|
const editionId = lastEdition['edition_id']
|
||||||
|
const isbn = lastEdition['isbn'] || null // get only from paper edition
|
||||||
|
|
||||||
|
return {
|
||||||
|
imageUrl: await this.getCoverFromEdition(editionId),
|
||||||
|
isbn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCoverFromEdition(editionId) {
|
||||||
|
if (!editionId) return null
|
||||||
|
const url = `${this._baseUrl}/edition/${editionId}`
|
||||||
|
|
||||||
|
const editionInfo = await axios.get(url).then((resp) => {
|
||||||
|
return resp.data || null
|
||||||
|
}).catch(error => {
|
||||||
|
Logger.error(`[FantLab] search cover from edition with url "${url}" error`, error)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
return editionInfo?.image || null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FantLab
|
Loading…
Reference in New Issue
Block a user