BookPage.js 8.3 KB

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