Работа над opds

This commit is contained in:
Book Pauk
2022-11-23 17:03:33 +07:00
parent 5a04e4f0c7
commit a8ed8b29e5
3 changed files with 214 additions and 26 deletions

View File

@@ -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({

View File

@@ -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;

View File

@@ -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);
}
}