BookPage.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. const path = require('path');
  2. const _ = require('lodash');
  3. const he = require('he');
  4. const dayjs = require('dayjs');
  5. const BasePage = require('./BasePage');
  6. const Fb2Parser = require('../fb2/Fb2Parser');
  7. class BookPage extends BasePage {
  8. constructor(config) {
  9. super(config);
  10. this.id = 'book';
  11. this.title = 'Книга';
  12. }
  13. formatSize(size) {
  14. size = size/1024;
  15. let unit = 'KB';
  16. if (size > 1024) {
  17. size = size/1024;
  18. unit = 'MB';
  19. }
  20. return `${size.toFixed(1)} ${unit}`;
  21. }
  22. inpxInfo(bookRec) {
  23. const mapping = [
  24. {name: 'fileInfo', label: 'Информация о файле', value: [
  25. {name: 'folder', label: 'Папка'},
  26. {name: 'file', label: 'Файл'},
  27. {name: 'size', label: 'Размер'},
  28. {name: 'date', label: 'Добавлен'},
  29. {name: 'del', label: 'Удален'},
  30. {name: 'libid', label: 'LibId'},
  31. {name: 'insno', label: 'InsideNo'},
  32. ]},
  33. {name: 'titleInfo', label: 'Общая информация', value: [
  34. {name: 'author', label: 'Автор(ы)'},
  35. {name: 'title', label: 'Название'},
  36. {name: 'series', label: 'Серия'},
  37. {name: 'genre', label: 'Жанр'},
  38. {name: 'librate', label: 'Оценка'},
  39. {name: 'lang', label: 'Язык книги'},
  40. {name: 'keywords', label: 'Ключевые слова'},
  41. ]},
  42. ];
  43. const valueToString = (value, nodePath, b) => {//eslint-disable-line no-unused-vars
  44. if (nodePath == 'fileInfo/file')
  45. return `${value}.${b.ext}`;
  46. if (nodePath == 'fileInfo/size')
  47. return `${this.formatSize(value)} (${value.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1 ')} Bytes)`;
  48. if (nodePath == 'fileInfo/date')
  49. return dayjs(value, 'YYYY-MM-DD').format('DD.MM.YYYY');
  50. if (nodePath == 'fileInfo/del')
  51. return (value ? 'Да' : null);
  52. if (nodePath == 'fileInfo/insno')
  53. return (value ? value : null);
  54. if (nodePath == 'titleInfo/author')
  55. return value.split(',').join(', ');
  56. if (nodePath == 'titleInfo/librate' && !value)
  57. return null;
  58. if (typeof(value) === 'string') {
  59. return value;
  60. }
  61. return (value.toString ? value.toString() : '');
  62. };
  63. let result = [];
  64. const book = _.cloneDeep(bookRec);
  65. book.series = [book.series, book.serno].filter(v => v).join(' #');
  66. for (const item of mapping) {
  67. const itemOut = {name: item.name, label: item.label, value: []};
  68. for (const subItem of item.value) {
  69. const subItemOut = {
  70. name: subItem.name,
  71. label: subItem.label,
  72. value: valueToString(book[subItem.name], `${item.name}/${subItem.name}`, book)
  73. };
  74. if (subItemOut.value)
  75. itemOut.value.push(subItemOut);
  76. }
  77. if (itemOut.value.length)
  78. result.push(itemOut);
  79. }
  80. return result;
  81. }
  82. htmlInfo(title, infoList) {
  83. let info = '';
  84. for (const part of infoList) {
  85. if (part.value.length)
  86. info += `<h3>${part.label}</h3>`;
  87. for (const rec of part.value)
  88. info += `<p>${rec.label}: ${rec.value}</p>`;
  89. }
  90. if (info)
  91. info = `<h2>${title}</h2>${info}`;
  92. return info;
  93. }
  94. async body(req) {
  95. const result = {};
  96. result.link = [
  97. this.navLink({rel: 'start'}),
  98. this.acqLink({rel: 'self', href: req.originalUrl, hrefAsIs: true}),
  99. ];
  100. const bookUid = req.query.uid;
  101. const entry = [];
  102. if (bookUid) {
  103. const {bookInfo} = await this.webWorker.getBookInfo(bookUid);
  104. if (bookInfo) {
  105. const {genreMap} = await this.getGenres();
  106. const fileFormat = `${bookInfo.book.ext}+zip`;
  107. //entry
  108. const e = this.makeEntry({
  109. id: bookUid,
  110. title: bookInfo.book.title || 'Без названия',
  111. });
  112. e['dc:language'] = bookInfo.book.lang;
  113. e['dc:format'] = fileFormat;
  114. //genre
  115. const genre = bookInfo.book.genre.split(',');
  116. for (const g of genre) {
  117. const genreName = genreMap.get(g);
  118. if (genreName) {
  119. if (!e.category)
  120. e.category = [];
  121. e.category.push({
  122. '*ATTRS': {term: genreName, label: genreName},
  123. });
  124. }
  125. }
  126. let content = '';
  127. let ann = '';
  128. let info = '';
  129. //fb2 info
  130. if (bookInfo.fb2) {
  131. const parser = new Fb2Parser(bookInfo.fb2);
  132. const infoObj = parser.bookInfo();
  133. if (infoObj.titleInfo) {
  134. if (infoObj.titleInfo.author.length) {
  135. e.author = infoObj.titleInfo.author.map(a => ({name: a}));
  136. }
  137. ann = infoObj.titleInfo.annotationHtml || '';
  138. const infoList = parser.bookInfoList(infoObj);
  139. info += this.htmlInfo('Fb2 инфо', infoList);
  140. }
  141. }
  142. //content
  143. info += this.htmlInfo('Inpx инфо', this.inpxInfo(bookInfo.book));
  144. content = `${ann}${info}`;
  145. if (content) {
  146. e.content = {
  147. '*ATTRS': {type: 'text/html'},
  148. '*TEXT': he.escape(content),
  149. };
  150. }
  151. //links
  152. e.link = [ this.downLink({href: bookInfo.link, type: `application/${fileFormat}`}) ];
  153. if (bookInfo.cover) {
  154. let coverType = 'image/jpeg';
  155. if (path.extname(bookInfo.cover) == '.png')
  156. coverType = 'image/png';
  157. e.link.push(this.imgLink({href: bookInfo.cover, type: coverType}));
  158. e.link.push(this.imgLink({href: bookInfo.cover, type: coverType, thumb: true}));
  159. }
  160. entry.push(e);
  161. }
  162. }
  163. result.entry = entry;
  164. return this.makeBody(result, req);
  165. }
  166. }
  167. module.exports = BookPage;