Работа над opds
This commit is contained in:
@@ -85,7 +85,7 @@ class AuthorPage extends BasePage {
|
||||
if (query.series) {
|
||||
//книги по серии
|
||||
const bookList = await this.webWorker.getSeriesBookList(query.series);
|
||||
|
||||
|
||||
if (bookList.books) {
|
||||
let books = JSON.parse(bookList.books);
|
||||
const filtered = (query.all ? books : this.filterBooks(books, query));
|
||||
@@ -96,6 +96,7 @@ class AuthorPage extends BasePage {
|
||||
if (query.all) {
|
||||
title = `${this.bookAuthor(book.author)} "${title}"`;
|
||||
}
|
||||
title += ` (${book.ext})`;
|
||||
|
||||
entry.push(
|
||||
this.makeEntry({
|
||||
|
||||
@@ -58,7 +58,7 @@ class BasePage {
|
||||
|
||||
acqLink(attrs) {
|
||||
return this.makeLink({
|
||||
href: this.opdsRoot + (attrs.href || ''),
|
||||
href: (attrs.hrefAsIs ? attrs.href : `${this.opdsRoot}${attrs.href || ''}`),
|
||||
rel: attrs.rel || 'subsection',
|
||||
type: 'application/atom+xml;profile=opds-catalog;kind=acquisition',
|
||||
});
|
||||
@@ -143,41 +143,43 @@ class BasePage {
|
||||
for (const row of queryRes.found)
|
||||
count += row.count;
|
||||
|
||||
if (count <= query.limit)
|
||||
return await this.search(from, query);
|
||||
|
||||
const result = [];
|
||||
const others = [];
|
||||
const names = new Set();
|
||||
for (const row of queryRes.found) {
|
||||
const name = row.name.toUpperCase();
|
||||
let result = [];
|
||||
if (count <= query.limit) {
|
||||
result = await this.search(from, query);
|
||||
} else {
|
||||
const names = new Set();
|
||||
for (const row of queryRes.found) {
|
||||
const name = row.name.toUpperCase();
|
||||
|
||||
if (!names.has(name)) {
|
||||
const rec = {
|
||||
id: row.id,
|
||||
title: name.replace(/ /g, spaceChar),
|
||||
q: encodeURIComponent(row.name.toLowerCase()),
|
||||
count: row.count,
|
||||
};
|
||||
if (query.depth > 1 || enru.has(row.name[0].toLowerCase())) {
|
||||
result.push(rec);
|
||||
} else {
|
||||
others.push(rec);
|
||||
if (!names.has(name)) {
|
||||
const rec = {
|
||||
id: row.id,
|
||||
title: name.replace(/ /g, spaceChar),
|
||||
q: encodeURIComponent(row.name.toLowerCase()),
|
||||
count: row.count,
|
||||
};
|
||||
if (query.depth > 1 || enru.has(row.name[0].toLowerCase())) {
|
||||
result.push(rec);
|
||||
} else {
|
||||
others.push(rec);
|
||||
}
|
||||
names.add(name);
|
||||
}
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (query.depth > 1 && result.length == 1 && query[from]) {
|
||||
const newQuery = _.cloneDeep(query);
|
||||
newQuery[from] = decodeURIComponent(result[0].q);
|
||||
|
||||
if (newQuery[from].length >= query.depth) {
|
||||
newQuery.depth = newQuery[from].length + 1;
|
||||
return await this.opdsQuery(from, newQuery);
|
||||
}
|
||||
}
|
||||
|
||||
if (!query.others && query.depth == 1)
|
||||
if (!query.others && others.length)
|
||||
result.push({id: 'other', title: 'Все остальные', q: '___others'});
|
||||
|
||||
return (!query.others ? result : others);
|
||||
@@ -291,6 +293,28 @@ class BasePage {
|
||||
});
|
||||
}
|
||||
|
||||
async getGenres() {
|
||||
let result;
|
||||
if (!this.genres) {
|
||||
const res = await this.webWorker.getGenreTree();
|
||||
|
||||
result = {
|
||||
genreTree: res.genreTree,
|
||||
genreMap: new Map(),
|
||||
};
|
||||
|
||||
for (const section of result.genreTree) {
|
||||
for (const g of section.value)
|
||||
result.genreMap.set(g.value, g.name);
|
||||
}
|
||||
|
||||
this.genres = result;
|
||||
} else {
|
||||
result = this.genres;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BasePage;
|
||||
@@ -1,5 +1,10 @@
|
||||
const path = require('path');
|
||||
const _ = require('lodash');
|
||||
const he = require('he');
|
||||
const dayjs = require('dayjs');
|
||||
|
||||
const BasePage = require('./BasePage');
|
||||
const Fb2Parser = require('../fb2/Fb2Parser');
|
||||
|
||||
class BookPage extends BasePage {
|
||||
constructor(config) {
|
||||
@@ -7,24 +12,181 @@ class BookPage extends BasePage {
|
||||
|
||||
this.id = 'book';
|
||||
this.title = 'Книга';
|
||||
|
||||
}
|
||||
|
||||
formatSize(size) {
|
||||
size = size/1024;
|
||||
let unit = 'KB';
|
||||
if (size > 1024) {
|
||||
size = size/1024;
|
||||
unit = 'MB';
|
||||
}
|
||||
return `${size.toFixed(1)} ${unit}`;
|
||||
}
|
||||
|
||||
inpxInfo(bookRec) {
|
||||
const mapping = [
|
||||
{name: 'fileInfo', label: 'Информация о файле', value: [
|
||||
{name: 'folder', label: 'Папка'},
|
||||
{name: 'file', label: 'Файл'},
|
||||
{name: 'size', label: 'Размер'},
|
||||
{name: 'date', label: 'Добавлен'},
|
||||
{name: 'del', label: 'Удален'},
|
||||
{name: 'libid', label: 'LibId'},
|
||||
{name: 'insno', label: 'InsideNo'},
|
||||
]},
|
||||
|
||||
{name: 'titleInfo', label: 'Общая информация', value: [
|
||||
{name: 'author', label: 'Автор(ы)'},
|
||||
{name: 'title', label: 'Название'},
|
||||
{name: 'series', label: 'Серия'},
|
||||
{name: 'genre', label: 'Жанр'},
|
||||
{name: 'librate', label: 'Оценка'},
|
||||
{name: 'lang', label: 'Язык книги'},
|
||||
{name: 'keywords', label: 'Ключевые слова'},
|
||||
]},
|
||||
];
|
||||
|
||||
const valueToString = (value, nodePath, b) => {//eslint-disable-line no-unused-vars
|
||||
if (nodePath == 'fileInfo/file')
|
||||
return `${value}.${b.ext}`;
|
||||
|
||||
if (nodePath == 'fileInfo/size')
|
||||
return `${this.formatSize(value)} (${value.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1 ')} Bytes)`;
|
||||
|
||||
if (nodePath == 'fileInfo/date')
|
||||
return dayjs(value, 'YYYY-MM-DD').format('DD.MM.YYYY');
|
||||
|
||||
if (nodePath == 'fileInfo/del')
|
||||
return (value ? 'Да' : null);
|
||||
|
||||
if (nodePath == 'fileInfo/insno')
|
||||
return (value ? value : null);
|
||||
|
||||
if (nodePath == 'titleInfo/author')
|
||||
return value.split(',').join(', ');
|
||||
|
||||
if (nodePath == 'titleInfo/librate' && !value)
|
||||
return null;
|
||||
|
||||
if (typeof(value) === 'string') {
|
||||
return value;
|
||||
}
|
||||
|
||||
return (value.toString ? value.toString() : '');
|
||||
};
|
||||
|
||||
let result = [];
|
||||
const book = _.cloneDeep(bookRec);
|
||||
book.series = [book.series, book.serno].filter(v => v).join(' #');
|
||||
|
||||
for (const item of mapping) {
|
||||
const itemOut = {name: item.name, label: item.label, value: []};
|
||||
|
||||
for (const subItem of item.value) {
|
||||
const subItemOut = {
|
||||
name: subItem.name,
|
||||
label: subItem.label,
|
||||
value: valueToString(book[subItem.name], `${item.name}/${subItem.name}`, book)
|
||||
};
|
||||
if (subItemOut.value)
|
||||
itemOut.value.push(subItemOut);
|
||||
}
|
||||
|
||||
if (itemOut.value.length)
|
||||
result.push(itemOut);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
htmlInfo(title, infoList) {
|
||||
let info = '';
|
||||
for (const part of infoList) {
|
||||
if (part.value.length)
|
||||
info += `<h3>${part.label}</h3>`;
|
||||
for (const rec of part.value)
|
||||
info += `<p>${rec.label}: ${rec.value}</p>`;
|
||||
}
|
||||
|
||||
if (info)
|
||||
info = `<h2>${title}</h2>${info}`;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
async body(req) {
|
||||
const result = {};
|
||||
|
||||
result.link = [
|
||||
this.navLink({rel: 'start'}),
|
||||
this.acqLink({rel: 'self', href: req.originalUrl, hrefAsIs: true}),
|
||||
];
|
||||
|
||||
const bookUid = req.query.uid;
|
||||
const entry = [];
|
||||
if (bookUid) {
|
||||
if (bookUid) {
|
||||
const {bookInfo} = await this.webWorker.getBookInfo(bookUid);
|
||||
|
||||
if (bookInfo) {
|
||||
const {genreMap} = await this.getGenres();
|
||||
const fileFormat = `${bookInfo.book.ext}+zip`;
|
||||
|
||||
//entry
|
||||
const e = this.makeEntry({
|
||||
id: bookUid,
|
||||
title: bookInfo.book.title || 'Без названия',
|
||||
link: [
|
||||
this.downLink({href: bookInfo.link, type: `application/${bookInfo.book.ext}+zip`}),
|
||||
],
|
||||
});
|
||||
|
||||
e['dc:language'] = bookInfo.book.lang;
|
||||
e['dc:format'] = fileFormat;
|
||||
|
||||
//genre
|
||||
const genre = bookInfo.book.genre.split(',');
|
||||
for (const g of genre) {
|
||||
const genreName = genreMap.get(g);
|
||||
if (genreName) {
|
||||
if (!e.category)
|
||||
e.category = [];
|
||||
e.category.push({
|
||||
'*ATTRS': {term: genreName, label: genreName},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let content = '';
|
||||
let ann = '';
|
||||
let info = '';
|
||||
//fb2 info
|
||||
if (bookInfo.fb2) {
|
||||
const parser = new Fb2Parser(bookInfo.fb2);
|
||||
const infoObj = parser.bookInfo();
|
||||
|
||||
if (infoObj.titleInfo) {
|
||||
if (infoObj.titleInfo.author.length) {
|
||||
e.author = infoObj.titleInfo.author.map(a => ({name: a}));
|
||||
}
|
||||
|
||||
ann = infoObj.titleInfo.annotationHtml || '';
|
||||
const infoList = parser.bookInfoList(infoObj);
|
||||
info += this.htmlInfo('Fb2 инфо', infoList);
|
||||
}
|
||||
}
|
||||
|
||||
//content
|
||||
info += this.htmlInfo('Inpx инфо', this.inpxInfo(bookInfo.book));
|
||||
|
||||
content = `${ann}${info}`;
|
||||
if (content) {
|
||||
e.content = {
|
||||
'*ATTRS': {type: 'text/html'},
|
||||
'*TEXT': he.escape(content),
|
||||
};
|
||||
}
|
||||
|
||||
//links
|
||||
e.link = [ this.downLink({href: bookInfo.link, type: `application/${fileFormat}`}) ];
|
||||
if (bookInfo.cover) {
|
||||
let coverType = 'image/jpeg';
|
||||
if (path.extname(bookInfo.cover) == '.png')
|
||||
@@ -39,6 +201,7 @@ class BookPage extends BasePage {
|
||||
}
|
||||
|
||||
result.entry = entry;
|
||||
|
||||
return this.makeBody(result, req);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user