From 2e99893c8bb4cde13a483486714c57c455fe173d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 02:54:34 +0000 Subject: [PATCH] Books: read DRM-free MOBI, AZW and AZW3; refuse Kindle files with DRM Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01UaC9HmtHMdRP8dAbN4wAKj --- README.md | 6 +- books.js | 9 ++- public/reader.js | 69 ++++++++++++++++---- public/vendor/reader/mobi-parser-LICENSE.txt | 21 ++++++ public/vendor/reader/mobi-parser.min.js | 16 +++++ tools/books-test.js | 8 ++- tools/mkbooks.py | 30 +++++++++ 7 files changed, 138 insertions(+), 21 deletions(-) create mode 100644 public/vendor/reader/mobi-parser-LICENSE.txt create mode 100644 public/vendor/reader/mobi-parser.min.js diff --git a/README.md b/README.md index 143307b..76ef293 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,10 @@ e.g. hub.necloud.us. Settings: `SEAFILE_URL`, `ONLYOFFICE_URL` env vars in docke script or logs. Anyone holding the link is signed in there with full access, so keep it out of the repo and `.env` backups. Or set `AUDIOBOOKS_SSO_SECRET` (and optionally `AUDIOBOOKS_SSO_URL`, default `/sso`) for short-lived signed links: `?token=.`, where `body` is base64url JSON `{u, id, exp, n}` and `sig` is the base64url HMAC-SHA256 of `body`. Without either, the plain site opens and the saved username can be copied. -- **Books**: an e-book reader for **EPUB, PDF and DOCX** (Media group; give it to people in Settings -> People as *Books*). Add files with the button or by dropping them on the page (up to 300 MB each; the server checks a file really is what its name says and does not store the same book twice). Titles, authors and covers are read out of the books in the browser; long-press or right-click a book to edit, download or delete it. Each person has their own shelf in `/data/users//books/`. - - **Reading**: EPUB uses epub.js (pages or scroll, two-page spreads on wide screens), PDF uses pdf.js (continuous scroll, zoom, selectable text, dark and sepia tints) and DOCX is converted to HTML by mammoth and shown in a frame that cannot run scripts (scroll only). Contents, search inside the book, a progress slider, tap the edges or use the arrow keys, swipe on touch screens, tap the middle to hide the bars. Appearance (theme, text size, font, line spacing, margins, layout, zoom) is remembered on each device. +- **Books**: an e-book reader for **EPUB, PDF, DOCX, MOBI, AZW and AZW3** (Media group; give it to people in Settings -> People as *Books*). Add files with the button or by dropping them on the page (up to 300 MB each; the server checks a file really is what its name says and does not store the same book twice). Titles, authors and covers are read out of the books in the browser; long-press or right-click a book to edit, download or delete it. Each person has their own shelf in `/data/users//books/`. + - **Reading**: EPUB uses epub.js (pages or scroll, two-page spreads on wide screens), PDF uses pdf.js (continuous scroll, zoom, selectable text, dark and sepia tints) DOCX is converted to HTML by mammoth, and MOBI, AZW and AZW3 are read by `@lingo-reader/mobi-parser`; both are shown in a frame that cannot run scripts (scroll only, with contents from the book or its headings). Contents, search inside the book, a progress slider, tap the edges or use the arrow keys, swipe on touch screens, tap the middle to hide the bars. Appearance (theme, text size, font, line spacing, margins, layout, zoom) is remembered on each device. - **Synced**: where you got to, bookmarks, highlights (four colours) and notes are kept on the server, so a book opens where you left off on any device; two devices adding notes at once keep both (the newest edit of the same note wins). Continue reading on the library page, sorting and search, and *Copy as text* exports a book's notes. - - The reader scripts are in `public/vendor/reader/` (epub.js, JSZip, pdf.js, mammoth) and load only when a book is opened or added. `node tools/books-test.js` tests the server; `python3 tools/mkbooks.py ` makes a small EPUB and PDF to try. Not included: DRM-protected books, MOBI/AZW, text-to-speech, offline reading. + - The reader scripts are in `public/vendor/reader/` (epub.js, JSZip, pdf.js, mammoth, the MOBI parser) and load only when a book is opened or added. `node tools/books-test.js` tests the server; `python3 tools/mkbooks.py ` makes a small EPUB, PDF and MOBI to try. **DRM-protected books are refused** (a Kindle file with copy protection is detected on upload and the reason is shown); only DRM-free books work. The MOBI reader was tried on a generated MOBI 6 file, not on real Kindle files, so tell me about any book that opens oddly. Not included: text-to-speech, offline reading. - **Signal**: your Signal chats inside the app, **on desktop-class screens only** (a window at least 900 px wide with a mouse or trackpad; phones and touch tablets don't see it). Besides the Signal tab there is a chat button on **every** screen that opens a panel over whatever you are doing (close it with the x; the expand button opens the full page). New messages get your attention with a toast, a chime, the unread count in the browser tab title and on the installed app's icon, and diff --git a/books.js b/books.js index 9a48c4d..1f698e6 100644 --- a/books.js +++ b/books.js @@ -4,7 +4,7 @@ const fs = require('fs'), path = require('path'), crypto = require('crypto'); const users = require('./users'); const MAX_BOOK = 300 * 1024 * 1024, MAX_COVER = 3 * 1024 * 1024, MAX_JSON = 2e6; -const TYPES = { epub: 'application/epub+zip', pdf: 'application/pdf', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }; +const TYPES = { epub: 'application/epub+zip', pdf: 'application/pdf', mobi: 'application/x-mobipocket-ebook', azw: 'application/vnd.amazon.ebook', azw3: 'application/vnd.amazon.mobi8-ebook', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }; const COVERS = { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/gif': 'gif', 'image/webp': 'webp' }; const dir = () => { const d = path.join(users.dir(), 'books'); fs.mkdirSync(d, { recursive: true, mode: 0o700 }); return d; }; @@ -23,9 +23,13 @@ async function body(req, max) { for await (const c of req) { n += c.length; if (n > max) return null; chunks.push(c); } return Buffer.concat(chunks); } +const MOBI = new Set(['mobi', 'azw', 'azw3']); +/** Kindle files with DRM have a non-zero encryption type in their first record (the PalmDOC header, 12 bytes in). Those cannot be read and are refused. */ +function mobiLocked(buf) { try { const rec0 = buf.readUInt32BE(78); return buf.readUInt16BE(rec0 + 12) !== 0; } catch { return true; } } /** A file must look like what its name says: PDFs start with %PDF, EPUB and DOCX are zip archives (and an EPUB says so in its first entry). */ function looksLike(ext, buf) { if (ext === 'pdf') return buf.subarray(0, 1024).includes('%PDF-'); + if (MOBI.has(ext)) return buf.length > 100 && buf.toString('latin1', 60, 68) === 'BOOKMOBI'; if (buf.length < 4 || buf[0] !== 0x50 || buf[1] !== 0x4b) return false; return ext === 'epub' ? buf.subarray(0, 200).includes('mimetypeapplication/epub+zip') || buf.includes('META-INF/container.xml') : buf.includes('word/document.xml'); } @@ -51,9 +55,10 @@ async function handle(req, res) { if (p === '/upload' && req.method === 'POST') { const name = clip(u.searchParams.get('name'), 200), ext = path.extname(name).slice(1).toLowerCase(); - if (!TYPES[ext]) return json(res, 400, { error: 'Only EPUB, PDF and DOCX books can be added' }); + if (!TYPES[ext]) return json(res, 400, { error: 'Only EPUB, PDF, DOCX, MOBI, AZW and AZW3 books can be added' }); const buf = await body(req, MAX_BOOK); if (!buf) return json(res, 413, { error: 'That file is larger than 300 MB' }); if (!looksLike(ext, buf)) return json(res, 400, { error: 'That does not look like a real ' + ext.toUpperCase() + ' file' }); + if (MOBI.has(ext) && mobiLocked(buf)) return json(res, 400, { error: 'This book has DRM (copy protection), which NE Hub cannot open. Only DRM-free books work.' }); const hash = crypto.createHash('sha1').update(buf).digest('hex'), list = index(), dup = list.find(b => b.hash === hash); if (dup) return json(res, 200, { ...dup, duplicate: true }); const id = crypto.randomBytes(8).toString('hex'); diff --git a/public/reader.js b/public/reader.js index 93c71d8..b775c08 100644 --- a/public/reader.js +++ b/public/reader.js @@ -3,7 +3,8 @@ // they follow you between devices. Reading uses epub.js, pdf.js and mammoth from /vendor/reader, loaded only when a book is opened or added. const rdApi = (p, o) => jfetch('/api/read' + p, o); const rdJson = (m, o) => ({ method: m, headers: { 'content-type': 'application/json' }, body: JSON.stringify(o) }); -const RD_LIBS = { epub: ['jszip.min.js', 'epub.min.js'], pdf: ['pdf.min.js'], docx: ['mammoth.browser.min.js'] }; +const RD_LIBS = { epub: ['jszip.min.js', 'epub.min.js'], pdf: ['pdf.min.js'], docx: ['mammoth.browser.min.js'], mobi: ['mobi-parser.min.js'] }; +const rdKind = ext => ['mobi', 'azw', 'azw3'].includes(ext) ? 'mobi' : ext; const rdLoaded = {}; async function rdLibs(kind) { for (const f of RD_LIBS[kind]) { @@ -43,8 +44,8 @@ async function rdLibrary() { const cover = b => `${b.cover ? `` : ''}${esc(b.title)}${esc(b.ext)}${b.pct > 0 ? `` : ''}`; const card = b => `${cover(b)}${esc(b.title)}${esc(b.author || b.ext.toUpperCase())}${b.pct > 0 ? ' ยท ' + Math.round(b.pct * 100) + '%' : ''}`; v.innerHTML = `

Books

- -

EPUB, PDF and DOCX. Add files with the button or drop them on this page. Your place, bookmarks and highlights follow you to every device.

`; + +

EPUB, PDF, DOCX, MOBI and AZW3 (without DRM). Add files with the button or drop them on this page. Your place, bookmarks and highlights follow you to every device.

`; const draw = () => { const f = q.trim().toLowerCase(), mine = books.filter(b => !f || (b.title + ' ' + b.author).toLowerCase().includes(f)); const sorted = [...mine].sort(sort === 'title' ? (a, b) => a.title.localeCompare(b.title) : sort === 'author' ? (a, b) => (a.author || '~').localeCompare(b.author || '~') || a.title.localeCompare(b.title) : sort === 'added' ? (a, b) => b.added - a.added : (a, b) => (b.read || b.added) - (a.read || a.added)); @@ -94,14 +95,14 @@ function rdEditBook(b) { // ---- Adding books: the file goes up as it is; the browser reads the title, author and cover out of it afterwards. async function rdAddAll(files) { - files = files.filter(f => /\.(epub|pdf|docx)$/i.test(f.name)); - if (!files.length) return toast('Choose EPUB, PDF or DOCX files'); - let added = 0, failed = 0; + files = files.filter(f => /\.(epub|pdf|docx|mobi|azw3?)$/i.test(f.name)); + if (!files.length) return toast('Choose EPUB, PDF, DOCX, MOBI or AZW3 files'); + let added = 0, failed = 0, why = ''; for (const [i, f] of files.entries()) { toast(`Adding ${i + 1} of ${files.length}: ${f.name}`); - try { if (!(await rdAddOne(f)).duplicate) added++; } catch (e) { failed++; toast(f.name + ': ' + e.message); await new Promise(r => setTimeout(r, 1800)); } + try { if (!(await rdAddOne(f)).duplicate) added++; } catch (e) { failed++; why = f.name + ': ' + e.message; toast(why); await new Promise(r => setTimeout(r, 1800)); } } - toast(added ? `Added ${added} book${added > 1 ? 's' : ''}${failed ? ', ' + failed + ' failed' : ''}` : failed ? 'Nothing was added' : 'Already in your library'); + toast(added ? `Added ${added} book${added > 1 ? 's' : ''}${failed ? ', ' + failed + ' failed' : ''}` : failed ? (files.length === 1 ? why : 'Nothing was added') : 'Already in your library'); if (location.hash.startsWith('#/read') && !location.hash.startsWith('#/read/open')) route(); } async function rdAddOne(file) { @@ -134,8 +135,18 @@ async function rdMeta(ext, buf) { } finally { pdf.destroy(); } return out; } + if (rdKind(ext) === 'mobi') { + await rdLibs('mobi'); const m = await rdMobiOpen(new Uint8Array(buf.slice(0))), out = {}; + try { const md = m.getMetadata(); out.title = md.title; out.author = (md.author || []).join(', '); const cu = m.getCoverImage(); if (cu) { const blob = await (await fetch(cu)).blob(); if (/^image\/(jpeg|png|gif|webp)$/.test(blob.type)) out.cover = blob; } } finally { try { m.destroy(); } catch {} } + return out; + } return {}; } +/** MOBI 6 and KF8 (AZW3) need different readers: try the newer format first, since the older reader can "open" a KF8 file and show nonsense. */ +async function rdMobiOpen(u8) { + try { const k = await MobiParser.initKf8File(u8); if (k.getSpine().length) return k; try { k.destroy(); } catch {} } catch {} + return MobiParser.initMobiFile(u8); +} // ======================================================= Reader let rdCur = null; // the open book's reader, so leaving the page can close it @@ -169,8 +180,8 @@ class RdReader { this.root = $('#rdroot'); this.applyChrome(); this.hookUi(); try { - await rdLibs(b.ext); - this.A = b.ext === 'epub' ? new RdEpub(this) : b.ext === 'pdf' ? new RdPdf(this) : new RdDocx(this); + await rdLibs(rdKind(b.ext)); + this.A = b.ext === 'epub' ? new RdEpub(this) : b.ext === 'pdf' ? new RdPdf(this) : rdKind(b.ext) === 'mobi' ? new RdMobi(this) : new RdDocx(this); await this.A.open(this.$('#rdstage'), this.loc); this.$('#rdload').remove(); } catch (e) { if (this.dead) return; this.$('#rdload').innerHTML = `${esc(e.message || 'This book could not be opened')}
Back to library`; } @@ -514,12 +525,15 @@ class RdPdf { // ======================================================= DOCX (converted to HTML in the browser; shown in a frame that cannot run scripts) class RdDocx { constructor(R) { this.R = R; } + async convert() { + const buf = await rdFetchBook(this.R.book.id, 'docx'); + return { html: (await mammoth.convertToHtml({ arrayBuffer: buf }, { styleMap: ['p[style-name="Title"] => h1.title:fresh', 'p[style-name="Subtitle"] => p.subtitle:fresh'] })).value, head: '' }; + } async open(stage, loc) { - const R = this.R, buf = await rdFetchBook(R.book.id, 'docx'); - const res = await mammoth.convertToHtml({ arrayBuffer: buf }, { styleMap: ['p[style-name="Title"] => h1.title:fresh', 'p[style-name="Subtitle"] => p.subtitle:fresh'] }); + const R = this.R, res = await this.convert(); this.f = document.createElement('iframe'); this.f.setAttribute('sandbox', 'allow-same-origin'); this.f.title = R.book.title; this.f.className = 'rdframe'; stage.appendChild(this.f); - this.f.srcdoc = `
${res.value}
`; + this.f.srcdoc = `${res.head}
${res.html}
`; await new Promise(ok => { this.f.onload = ok; }); this.d = this.f.contentDocument; this.w = this.f.contentWindow; this.root = this.d.documentElement; this.restyle(); @@ -527,12 +541,13 @@ class RdDocx { this.d.addEventListener('mousedown', () => R.hideSel()); const sel = () => setTimeout(() => this.checkSel(), 30); this.d.addEventListener('mouseup', sel); this.d.addEventListener('touchend', sel); this.d.addEventListener('keyup', sel); - this.d.addEventListener('click', e => { const s = this.w.getSelection(); if (s && !s.isCollapsed) return; if (e.target.closest('a[href]')) { e.preventDefault(); const a = e.target.closest('a[href]'); if (a.getAttribute('href').startsWith('#')) this.d.getElementById(a.getAttribute('href').slice(1))?.scrollIntoView(); return; } R.toggleBars(); }); + this.d.addEventListener('click', e => { const s = this.w.getSelection(); if (s && !s.isCollapsed) return; if (e.target.closest('a[href]')) { e.preventDefault(); const a = e.target.closest('a[href]'); if (!this.link(a)) this.d.getElementById((a.getAttribute('href') || '').replace(/^#/, ''))?.scrollIntoView(); return; } R.toggleBars(); }); this.d.addEventListener('scroll', () => { if (!this.raf) this.raf = requestAnimationFrame(() => { this.raf = 0; this.report(); }); }, { passive: true }); this.w.addEventListener('scroll', () => { if (!this.raf) this.raf = requestAnimationFrame(() => { this.raf = 0; this.report(); }); }, { passive: true }); for (const n of R.notes) this.drawNote(n); const m = /^o:(\d+)/.exec(loc || ''); if (m) this.gotoOffset(+m[1]); this.report(); } + link() { return false; } scroller() { return this.d.scrollingElement || this.root; } restyle(k) { const R = this.R, t = rdTheme(R.P); this.d.getElementById('theme').textContent = rdCss(R.P, t); R.root.style.setProperty('--rd-bg', t.bg); @@ -577,3 +592,29 @@ class RdDocx { resize() {} destroy() {} } + +// ======================================================= MOBI / AZW / AZW3 (DRM-free): every chapter is joined into one page, then read like a DOCX +class RdMobi extends RdDocx { + async convert() { + const buf = new Uint8Array(await rdFetchBook(this.R.book.id, 'mobi')); + this.m = await rdMobiOpen(buf); const spine = this.m.getSpine(), css = new Set(), parts = []; + for (const ch of spine) { const c = this.m.loadChapter(ch.id); if (!c) continue; (c.css || []).forEach(x => css.add(x.href)); parts.push(`
${c.html}
`); } + if (!parts.length) throw new Error('This book has no readable text. It may have DRM.'); + return { html: parts.join(''), head: [...css].map(h => ``).join('') }; + } + /** a link inside the book: the parser says which chapter (and where in it) it points to */ + link(a) { + const href = a.getAttribute('href') || ''; if (/^(https?:|mailto:)/i.test(href)) { window.open(href, '_blank', 'noopener'); return true; } + let r; try { r = this.m.resolveHref(href); } catch {} if (!r) return false; + const sec = this.d.getElementById('rdch-' + r.id); let el = sec; if (sec && r.selector) { try { el = sec.querySelector(r.selector) || sec; } catch {} } + if (el) { el.scrollIntoView(); return true; } return false; + } + async toc() { + const out = [], walk = (items, d) => (items || []).forEach(t => { + let loc = null; try { const r = this.m.resolveHref(t.href); const sec = r && this.d.getElementById('rdch-' + r.id); let el = sec; if (sec && r.selector) { try { el = sec.querySelector(r.selector) || sec; } catch {} } if (el) { const w = this.d.createTreeWalker(el, NodeFilter.SHOW_TEXT), first = w.nextNode(); if (first) loc = 'o:' + this.offsetTo(first, 0); } } catch {} + if (loc) out.push({ label: String(t.label || '').trim().slice(0, 120) || 'Untitled', loc, depth: d }); walk(t.children, d + 1); }); + walk(this.m.getToc(), 0); + return out.length ? out : super.toc(); + } + destroy() { try { this.m && this.m.destroy(); } catch {} } +} diff --git a/public/vendor/reader/mobi-parser-LICENSE.txt b/public/vendor/reader/mobi-parser-LICENSE.txt new file mode 100644 index 0000000..ce4c9a6 --- /dev/null +++ b/public/vendor/reader/mobi-parser-LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 hhk-png + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/public/vendor/reader/mobi-parser.min.js b/public/vendor/reader/mobi-parser.min.js new file mode 100644 index 0000000..a132368 --- /dev/null +++ b/public/vendor/reader/mobi-parser.min.js @@ -0,0 +1,16 @@ +var MobiParser=(()=>{var Ut=Object.create;var Se=Object.defineProperty;var Mt=Object.getOwnPropertyDescriptor;var zt=Object.getOwnPropertyNames;var kt=Object.getPrototypeOf,Ht=Object.prototype.hasOwnProperty;var Gt=(r=>typeof require!="undefined"?require:typeof Proxy!="undefined"?new Proxy(r,{get:(e,t)=>(typeof require!="undefined"?require:e)[t]}):r)(function(r){if(typeof require!="undefined")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});var Me=(r,e)=>()=>{try{return e||r((e={exports:{}}).exports,e),e.exports}catch(t){throw e=0,t}},jt=(r,e)=>{for(var t in e)Se(r,t,{get:e[t],enumerable:!0})},Ze=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of zt(e))!Ht.call(r,o)&&o!==t&&Se(r,o,{get:()=>e[o],enumerable:!(s=Mt(e,o))||s.enumerable});return r};var ze=(r,e,t)=>(t=r!=null?Ut(kt(r)):{},Ze(e||!r||!r.__esModule?Se(t,"default",{value:r,enumerable:!0}):t,r)),Vt=r=>Ze(Se({},"__esModule",{value:!0}),r);var at=Me((sr,ot)=>{"use strict";function ie(r){if(typeof r!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(r))}function st(r,e){for(var t="",s=0,o=-1,l=0,a,u=0;u<=r.length;++u){if(u2){var d=t.lastIndexOf("/");if(d!==t.length-1){d===-1?(t="",s=0):(t=t.slice(0,d),s=t.length-1-t.lastIndexOf("/")),o=u,l=0;continue}}else if(t.length===2||t.length===1){t="",s=0,o=u,l=0;continue}}e&&(t.length>0?t+="/..":t="..",s=2)}else t.length>0?t+="/"+r.slice(o+1,u):t=r.slice(o+1,u),s=u-o-1;o=u,l=0}else a===46&&l!==-1?++l:l=-1}return t}function on(r,e){var t=e.dir||e.root,s=e.base||(e.name||"")+(e.ext||"");return t?t===e.root?t+s:t+r+s:s}var he={resolve:function(){for(var e="",t=!1,s,o=arguments.length-1;o>=-1&&!t;o--){var l;o>=0?l=arguments[o]:(s===void 0&&(s=process.cwd()),l=s),ie(l),l.length!==0&&(e=l+"/"+e,t=l.charCodeAt(0)===47)}return e=st(e,!t),t?e.length>0?"/"+e:"/":e.length>0?e:"."},normalize:function(e){if(ie(e),e.length===0)return".";var t=e.charCodeAt(0)===47,s=e.charCodeAt(e.length-1)===47;return e=st(e,!t),e.length===0&&!t&&(e="."),e.length>0&&s&&(e+="/"),t?"/"+e:e},isAbsolute:function(e){return ie(e),e.length>0&&e.charCodeAt(0)===47},join:function(){if(arguments.length===0)return".";for(var e,t=0;t0&&(e===void 0?e=s:e+="/"+s)}return e===void 0?".":he.normalize(e)},relative:function(e,t){if(ie(e),ie(t),e===t||(e=he.resolve(e),t=he.resolve(t),e===t))return"";for(var s=1;sm){if(t.charCodeAt(a+g)===47)return t.slice(a+g+1);if(g===0)return t.slice(a+g)}else l>m&&(e.charCodeAt(s+g)===47?v=g:g===0&&(v=0));break}var p=e.charCodeAt(s+g),y=t.charCodeAt(a+g);if(p!==y)break;p===47&&(v=g)}var b="";for(g=s+v+1;g<=o;++g)(g===o||e.charCodeAt(g)===47)&&(b.length===0?b+="..":b+="/..");return b.length>0?b+t.slice(a+v):(a+=v,t.charCodeAt(a)===47&&++a,t.slice(a))},_makeLong:function(e){return e},dirname:function(e){if(ie(e),e.length===0)return".";for(var t=e.charCodeAt(0),s=t===47,o=-1,l=!0,a=e.length-1;a>=1;--a)if(t=e.charCodeAt(a),t===47){if(!l){o=a;break}}else l=!1;return o===-1?s?"/":".":s&&o===1?"//":e.slice(0,o)},basename:function(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');ie(e);var s=0,o=-1,l=!0,a;if(t!==void 0&&t.length>0&&t.length<=e.length){if(t.length===e.length&&t===e)return"";var u=t.length-1,d=-1;for(a=e.length-1;a>=0;--a){var m=e.charCodeAt(a);if(m===47){if(!l){s=a+1;break}}else d===-1&&(l=!1,d=a+1),u>=0&&(m===t.charCodeAt(u)?--u===-1&&(o=a):(u=-1,o=d))}return s===o?o=d:o===-1&&(o=e.length),e.slice(s,o)}else{for(a=e.length-1;a>=0;--a)if(e.charCodeAt(a)===47){if(!l){s=a+1;break}}else o===-1&&(l=!1,o=a+1);return o===-1?"":e.slice(s,o)}},extname:function(e){ie(e);for(var t=-1,s=0,o=-1,l=!0,a=0,u=e.length-1;u>=0;--u){var d=e.charCodeAt(u);if(d===47){if(!l){s=u+1;break}continue}o===-1&&(l=!1,o=u+1),d===46?t===-1?t=u:a!==1&&(a=1):t!==-1&&(a=-1)}return t===-1||o===-1||a===0||a===1&&t===o-1&&t===s+1?"":e.slice(t,o)},format:function(e){if(e===null||typeof e!="object")throw new TypeError('The "pathObject" argument must be of type Object. Received type '+typeof e);return on("/",e)},parse:function(e){ie(e);var t={root:"",dir:"",base:"",ext:"",name:""};if(e.length===0)return t;var s=e.charCodeAt(0),o=s===47,l;o?(t.root="/",l=1):l=0;for(var a=-1,u=0,d=-1,m=!0,v=e.length-1,g=0;v>=l;--v){if(s=e.charCodeAt(v),s===47){if(!m){u=v+1;break}continue}d===-1&&(m=!1,d=v+1),s===46?a===-1?a=v:g!==1&&(g=1):a!==-1&&(g=-1)}return a===-1||d===-1||g===0||g===1&&a===d-1&&a===u+1?d!==-1&&(u===0&&o?t.base=t.name=e.slice(1,d):t.base=t.name=e.slice(u,d)):(u===0&&o?(t.name=e.slice(1,a),t.base=e.slice(1,d)):(t.name=e.slice(u,a),t.base=e.slice(u,d)),t.ext=e.slice(a,d)),u>0?t.dir=e.slice(0,u-1):o&&(t.dir="/"),t},sep:"/",delimiter:":",win32:null,posix:null};he.posix=he;ot.exports=he});var yt=Me((or,je)=>{"use strict";var ge=typeof Reflect=="object"?Reflect:null,ct=ge&&typeof ge.apply=="function"?ge.apply:function(e,t,s){return Function.prototype.apply.call(e,t,s)},De;ge&&typeof ge.ownKeys=="function"?De=ge.ownKeys:Object.getOwnPropertySymbols?De=function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:De=function(e){return Object.getOwnPropertyNames(e)};function an(r){console&&console.warn&&console.warn(r)}var ft=Number.isNaN||function(e){return e!==e};function D(){D.init.call(this)}je.exports=D;je.exports.once=un;D.EventEmitter=D;D.prototype._events=void 0;D.prototype._eventsCount=0;D.prototype._maxListeners=void 0;var lt=10;function Oe(r){if(typeof r!="function")throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof r)}Object.defineProperty(D,"defaultMaxListeners",{enumerable:!0,get:function(){return lt},set:function(r){if(typeof r!="number"||r<0||ft(r))throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received '+r+".");lt=r}});D.init=function(){(this._events===void 0||this._events===Object.getPrototypeOf(this)._events)&&(this._events=Object.create(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0};D.prototype.setMaxListeners=function(e){if(typeof e!="number"||e<0||ft(e))throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received '+e+".");return this._maxListeners=e,this};function ut(r){return r._maxListeners===void 0?D.defaultMaxListeners:r._maxListeners}D.prototype.getMaxListeners=function(){return ut(this)};D.prototype.emit=function(e){for(var t=[],s=1;s0&&(a=t[0]),a instanceof Error)throw a;var u=new Error("Unhandled error."+(a?" ("+a.message+")":""));throw u.context=a,u}var d=l[e];if(d===void 0)return!1;if(typeof d=="function")ct(d,this,t);else for(var m=d.length,v=pt(d,m),s=0;s0&&a.length>o&&!a.warned){a.warned=!0;var u=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+String(e)+" listeners added. Use emitter.setMaxListeners() to increase limit");u.name="MaxListenersExceededWarning",u.emitter=r,u.type=e,u.count=a.length,an(u)}return r}D.prototype.addListener=function(e,t){return ht(this,e,t,!1)};D.prototype.on=D.prototype.addListener;D.prototype.prependListener=function(e,t){return ht(this,e,t,!0)};function cn(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,arguments.length===0?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function gt(r,e,t){var s={fired:!1,wrapFn:void 0,target:r,type:e,listener:t},o=cn.bind(s);return o.listener=t,s.wrapFn=o,o}D.prototype.once=function(e,t){return Oe(t),this.on(e,gt(this,e,t)),this};D.prototype.prependOnceListener=function(e,t){return Oe(t),this.prependListener(e,gt(this,e,t)),this};D.prototype.removeListener=function(e,t){var s,o,l,a,u;if(Oe(t),o=this._events,o===void 0)return this;if(s=o[e],s===void 0)return this;if(s===t||s.listener===t)--this._eventsCount===0?this._events=Object.create(null):(delete o[e],o.removeListener&&this.emit("removeListener",e,s.listener||t));else if(typeof s!="function"){for(l=-1,a=s.length-1;a>=0;a--)if(s[a]===t||s[a].listener===t){u=s[a].listener,l=a;break}if(l<0)return this;l===0?s.shift():ln(s,l),s.length===1&&(o[e]=s[0]),o.removeListener!==void 0&&this.emit("removeListener",e,u||t)}return this};D.prototype.off=D.prototype.removeListener;D.prototype.removeAllListeners=function(e){var t,s,o;if(s=this._events,s===void 0)return this;if(s.removeListener===void 0)return arguments.length===0?(this._events=Object.create(null),this._eventsCount=0):s[e]!==void 0&&(--this._eventsCount===0?this._events=Object.create(null):delete s[e]),this;if(arguments.length===0){var l=Object.keys(s),a;for(o=0;o=0;o--)this.removeListener(e,t[o]);return this};function dt(r,e,t){var s=r._events;if(s===void 0)return[];var o=s[e];return o===void 0?[]:typeof o=="function"?t?[o.listener||o]:[o]:t?fn(o):pt(o,o.length)}D.prototype.listeners=function(e){return dt(this,e,!0)};D.prototype.rawListeners=function(e){return dt(this,e,!1)};D.listenerCount=function(r,e){return typeof r.listenerCount=="function"?r.listenerCount(e):vt.call(r,e)};D.prototype.listenerCount=vt;function vt(r){var e=this._events;if(e!==void 0){var t=e[r];if(typeof t=="function")return 1;if(t!==void 0)return t.length}return 0}D.prototype.eventNames=function(){return this._eventsCount>0?De(this._events):[]};function pt(r,e){for(var t=new Array(e),s=0;s{(function(r){r.parser=function(i,n){return new t(i,n)},r.SAXParser=t,r.SAXStream=v,r.createStream=d,r.MAX_BUFFER_LENGTH=64*1024;var e=["comment","sgmlDecl","textNode","tagName","doctype","procInstName","procInstBody","entity","attribName","attribValue","cdata","script"];r.EVENTS=["text","processinginstruction","sgmldeclaration","doctype","comment","opentagstart","attribute","opentag","closetag","opencdata","cdata","closecdata","error","end","ready","script","opennamespace","closenamespace"];function t(i,n){if(!(this instanceof t))return new t(i,n);var h=this;o(h),h.q=h.c="",h.bufferCheckPosition=r.MAX_BUFFER_LENGTH,h.encoding=null,h.opt=n||{},h.opt.lowercase=h.opt.lowercase||h.opt.lowercasetags,h.looseCase=h.opt.lowercase?"toLowerCase":"toUpperCase",h.opt.maxEntityCount=h.opt.maxEntityCount||512,h.opt.maxEntityDepth=h.opt.maxEntityDepth||4,h.entityCount=h.entityDepth=0,h.tags=[],h.closed=h.closedRoot=h.sawRoot=!1,h.tag=h.error=null,h.strict=!!i,h.noscript=!!(i||h.opt.noscript),h.state=f.BEGIN,h.strictEntities=h.opt.strictEntities,h.ENTITIES=h.strictEntities?Object.create(r.XML_ENTITIES):Object.create(r.ENTITIES),h.attribList=[],h.opt.xmlns&&(h.ns=Object.create(T)),h.opt.unquotedAttributeValues===void 0&&(h.opt.unquotedAttributeValues=!i),h.trackPosition=h.opt.position!==!1,h.trackPosition&&(h.position=h.line=h.column=0),_(h,"onready")}Object.create||(Object.create=function(i){function n(){}n.prototype=i;var h=new n;return h}),Object.keys||(Object.keys=function(i){var n=[];for(var h in i)i.hasOwnProperty(h)&&n.push(h);return n});function s(i){for(var n=Math.max(r.MAX_BUFFER_LENGTH,10),h=0,c=0,S=e.length;cn)switch(e[c]){case"textNode":U(i);break;case"cdata":A(i,"oncdata",i.cdata),i.cdata="";break;case"script":A(i,"onscript",i.script),i.script="";break;default:z(i,"Max buffer length exceeded: "+e[c])}h=Math.max(h,L)}var P=r.MAX_BUFFER_LENGTH-h;i.bufferCheckPosition=P+i.position}function o(i){for(var n=0,h=e.length;n=2){if(i[0]===255&&i[1]===254)return"utf-16le";if(i[0]===254&&i[1]===255)return"utf-16be"}return i.length>=3&&i[0]===239&&i[1]===187&&i[2]===191?"utf8":i.length>=4?i[0]===60&&i[1]===0&&i[2]===63&&i[3]===0?"utf-16le":i[0]===0&&i[1]===60&&i[2]===0&&i[3]===63?"utf-16be":"utf8":n?"utf8":null}function v(i,n){if(!(this instanceof v))return new v(i,n);a.apply(this),this._parser=new t(i,n),this.writable=!0,this.readable=!0;var h=this;this._parser.onend=function(){h.emit("end")},this._parser.onerror=function(c){h.emit("error",c),h._parser.error=null},this._decoder=null,this._decoderBuffer=null,u.forEach(function(c){Object.defineProperty(h,"on"+c,{get:function(){return h._parser["on"+c]},set:function(S){if(!S)return h.removeAllListeners(c),h._parser["on"+c]=S,S;h.on(c,S)},enumerable:!0,configurable:!1})})}v.prototype=Object.create(a.prototype,{constructor:{value:v}}),v.prototype._decodeBuffer=function(i,n){if(this._decoderBuffer&&(i=Buffer.concat([this._decoderBuffer,i]),this._decoderBuffer=null),!this._decoder){var h=m(i,n);if(!h)return this._decoderBuffer=i,"";this._parser.encoding=h,this._decoder=new TextDecoder(h)}return this._decoder.decode(i,{stream:!n})},v.prototype.write=function(i){if(typeof Buffer=="function"&&typeof Buffer.isBuffer=="function"&&Buffer.isBuffer(i))i=this._decodeBuffer(i,!1);else if(this._decoderBuffer){var n=this._decodeBuffer(Buffer.alloc(0),!0);n&&(this._parser.write(n),this.emit("data",n))}return this._parser.write(i.toString()),this.emit("data",i),!0},v.prototype.end=function(i){if(i&&i.length&&this.write(i),this._decoderBuffer){var n=this._decodeBuffer(Buffer.alloc(0),!0);n&&(this._parser.write(n),this.emit("data",n))}else if(this._decoder){var h=this._decoder.decode();h&&(this._parser.write(h),this.emit("data",h))}return this._parser.end(),!0},v.prototype.on=function(i,n){var h=this;return!h._parser["on"+i]&&u.indexOf(i)!==-1&&(h._parser["on"+i]=function(){var c=arguments.length===1?[arguments[0]]:Array.apply(null,arguments);c.splice(0,0,i),h.emit.apply(h,c)}),a.prototype.on.call(h,i,n)};var g=/^\[CDATA\[$/i,p=/^DOCTYPE$/i,y="http://www.w3.org/XML/1998/namespace",b="http://www.w3.org/2000/xmlns/",T={xml:y,xmlns:b},w=/[:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/,C=/[:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u00B7\u0300-\u036F\u203F-\u2040.\d-]/,O=/[#:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/,E=/[#:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u00B7\u0300-\u036F\u203F-\u2040.\d-]/;function I(i){return i===" "||i===` +`||i==="\r"||i===" "}function R(i){return i==='"'||i==="'"}function X(i){return i===">"||I(i)}function F(i,n){return i.test(n)}function k(i,n){return!F(i,n)}var f=0;r.STATE={BEGIN:f++,BEGIN_WHITESPACE:f++,TEXT:f++,TEXT_ENTITY:f++,OPEN_WAKA:f++,SGML_DECL:f++,SGML_DECL_QUOTED:f++,DOCTYPE:f++,DOCTYPE_QUOTED:f++,DOCTYPE_DTD:f++,DOCTYPE_DTD_QUOTED:f++,COMMENT_STARTING:f++,COMMENT:f++,COMMENT_ENDING:f++,COMMENT_ENDED:f++,CDATA:f++,CDATA_ENDING:f++,CDATA_ENDING_2:f++,PROC_INST:f++,PROC_INST_BODY:f++,PROC_INST_ENDING:f++,OPEN_TAG:f++,OPEN_TAG_SLASH:f++,ATTRIB:f++,ATTRIB_NAME:f++,ATTRIB_NAME_SAW_WHITE:f++,ATTRIB_VALUE:f++,ATTRIB_VALUE_QUOTED:f++,ATTRIB_VALUE_CLOSED:f++,ATTRIB_VALUE_UNQUOTED:f++,ATTRIB_VALUE_ENTITY_Q:f++,ATTRIB_VALUE_ENTITY_U:f++,CLOSE_TAG:f++,CLOSE_TAG_SAW_WHITE:f++,SCRIPT:f++,SCRIPT_ENDING:f++},r.XML_ENTITIES=Object.assign(Object.create(null),{amp:"&",gt:">",lt:"<",quot:'"',apos:"'"}),r.ENTITIES=Object.assign(Object.create(null),{amp:"&",gt:">",lt:"<",quot:'"',apos:"'",AElig:198,Aacute:193,Acirc:194,Agrave:192,Aring:197,Atilde:195,Auml:196,Ccedil:199,ETH:208,Eacute:201,Ecirc:202,Egrave:200,Euml:203,Iacute:205,Icirc:206,Igrave:204,Iuml:207,Ntilde:209,Oacute:211,Ocirc:212,Ograve:210,Oslash:216,Otilde:213,Ouml:214,THORN:222,Uacute:218,Ucirc:219,Ugrave:217,Uuml:220,Yacute:221,aacute:225,acirc:226,aelig:230,agrave:224,aring:229,atilde:227,auml:228,ccedil:231,eacute:233,ecirc:234,egrave:232,eth:240,euml:235,iacute:237,icirc:238,igrave:236,iuml:239,ntilde:241,oacute:243,ocirc:244,ograve:242,oslash:248,otilde:245,ouml:246,szlig:223,thorn:254,uacute:250,ucirc:251,ugrave:249,uuml:252,yacute:253,yuml:255,copy:169,reg:174,nbsp:160,iexcl:161,cent:162,pound:163,curren:164,yen:165,brvbar:166,sect:167,uml:168,ordf:170,laquo:171,not:172,shy:173,macr:175,deg:176,plusmn:177,sup1:185,sup2:178,sup3:179,acute:180,micro:181,para:182,middot:183,cedil:184,ordm:186,raquo:187,frac14:188,frac12:189,frac34:190,iquest:191,times:215,divide:247,OElig:338,oelig:339,Scaron:352,scaron:353,Yuml:376,fnof:402,circ:710,tilde:732,Alpha:913,Beta:914,Gamma:915,Delta:916,Epsilon:917,Zeta:918,Eta:919,Theta:920,Iota:921,Kappa:922,Lambda:923,Mu:924,Nu:925,Xi:926,Omicron:927,Pi:928,Rho:929,Sigma:931,Tau:932,Upsilon:933,Phi:934,Chi:935,Psi:936,Omega:937,alpha:945,beta:946,gamma:947,delta:948,epsilon:949,zeta:950,eta:951,theta:952,iota:953,kappa:954,lambda:955,mu:956,nu:957,xi:958,omicron:959,pi:960,rho:961,sigmaf:962,sigma:963,tau:964,upsilon:965,phi:966,chi:967,psi:968,omega:969,thetasym:977,upsih:978,piv:982,ensp:8194,emsp:8195,thinsp:8201,zwnj:8204,zwj:8205,lrm:8206,rlm:8207,ndash:8211,mdash:8212,lsquo:8216,rsquo:8217,sbquo:8218,ldquo:8220,rdquo:8221,bdquo:8222,dagger:8224,Dagger:8225,bull:8226,hellip:8230,permil:8240,prime:8242,Prime:8243,lsaquo:8249,rsaquo:8250,oline:8254,frasl:8260,euro:8364,image:8465,weierp:8472,real:8476,trade:8482,alefsym:8501,larr:8592,uarr:8593,rarr:8594,darr:8595,harr:8596,crarr:8629,lArr:8656,uArr:8657,rArr:8658,dArr:8659,hArr:8660,forall:8704,part:8706,exist:8707,empty:8709,nabla:8711,isin:8712,notin:8713,ni:8715,prod:8719,sum:8721,minus:8722,lowast:8727,radic:8730,prop:8733,infin:8734,ang:8736,and:8743,or:8744,cap:8745,cup:8746,int:8747,there4:8756,sim:8764,cong:8773,asymp:8776,ne:8800,equiv:8801,le:8804,ge:8805,sub:8834,sup:8835,nsub:8836,sube:8838,supe:8839,oplus:8853,otimes:8855,perp:8869,sdot:8901,lceil:8968,rceil:8969,lfloor:8970,rfloor:8971,lang:9001,rang:9002,loz:9674,spades:9824,clubs:9827,hearts:9829,diams:9830}),Object.keys(r.ENTITIES).forEach(function(i){var n=r.ENTITIES[i],h=typeof n=="number"?String.fromCharCode(n):n;r.ENTITIES[i]=h});for(var W in r.STATE)r.STATE[r.STATE[W]]=W;f=r.STATE;function _(i,n,h){i[n]&&i[n](h)}function J(i){var n=i&&i.match(/(?:^|\s)encoding\s*=\s*(['"])([^'"]+)\1/i);return n?n[2]:null}function ce(i){return i?i.toLowerCase().replace(/[^a-z0-9]/g,""):null}function ne(i,n){let h=ce(i),c=ce(n);return!h||!c?!0:c==="utf16"?h==="utf16le"||h==="utf16be":h===c}function Y(i,n){if(!(!i.strict||!i.encoding||!n||n.name!=="xml")){var h=J(n.body);h&&!ne(i.encoding,h)&&N(i,"XML declaration encoding "+h+" does not match detected stream encoding "+i.encoding.toUpperCase())}}function A(i,n,h){i.textNode&&U(i),_(i,n,h)}function U(i){i.textNode=pe(i.opt,i.textNode),i.textNode&&_(i,"ontext",i.textNode),i.textNode=""}function pe(i,n){return i.trim&&(n=n.trim()),i.normalize&&(n=n.replace(/\s+/g," ")),n}function z(i,n){return U(i),i.trackPosition&&(n+=` +Line: `+i.line+` +Column: `+i.column+` +Char: `+i.c),n=new Error(n),i.error=n,_(i,"onerror",n),i}function xe(i){return i.sawRoot&&!i.closedRoot&&N(i,"Unclosed root tag"),i.state!==f.BEGIN&&i.state!==f.BEGIN_WHITESPACE&&i.state!==f.TEXT&&z(i,"Unexpected end"),U(i),i.c="",i.closed=!0,_(i,"onend"),t.call(i,i.strict,i.opt),i}function N(i,n){if(typeof i!="object"||!(i instanceof t))throw new Error("bad call to strictFail");i.strict&&z(i,n)}function me(i){i.strict||(i.tagName=i.tagName[i.looseCase]());var n=i.tags[i.tags.length-1]||i,h=i.tag={name:i.tagName,attributes:{}};i.opt.xmlns&&(h.ns=n.ns),i.attribList.length=0,A(i,"onopentagstart",h)}function re(i,n){var h=i.indexOf(":"),c=h<0?["",i]:i.split(":"),S=c[0],L=c[1];return n&&i==="xmlns"&&(S="xmlns",L=""),{prefix:S,local:L}}function fe(i){if(i.strict||(i.attribName=i.attribName[i.looseCase]()),i.attribList.indexOf(i.attribName)!==-1||i.tag.attributes.hasOwnProperty(i.attribName)){i.attribName=i.attribValue="";return}if(i.opt.xmlns){var n=re(i.attribName,!0),h=n.prefix,c=n.local;if(h==="xmlns")if(c==="xml"&&i.attribValue!==y)N(i,"xml: prefix must be bound to "+y+` +Actual: `+i.attribValue);else if(c==="xmlns"&&i.attribValue!==b)N(i,"xmlns: prefix must be bound to "+b+` +Actual: `+i.attribValue);else{var S=i.tag,L=i.tags[i.tags.length-1]||i;S.ns===L.ns&&(S.ns=Object.create(L.ns)),S.ns[c]=i.attribValue}i.attribList.push([i.attribName,i.attribValue])}else i.tag.attributes[i.attribName]=i.attribValue,A(i,"onattribute",{name:i.attribName,value:i.attribValue});i.attribName=i.attribValue=""}function q(i,n){if(i.opt.xmlns){var h=i.tag,c=re(i.tagName);h.prefix=c.prefix,h.local=c.local,h.uri=h.ns[c.prefix]||"",h.prefix&&!h.uri&&(N(i,"Unbound namespace prefix: "+JSON.stringify(i.tagName)),h.uri=c.prefix);var S=i.tags[i.tags.length-1]||i;h.ns&&S.ns!==h.ns&&Object.keys(h.ns).forEach(function($e){A(i,"onopennamespace",{prefix:$e,uri:h.ns[$e]})});for(var L=0,P=i.attribList.length;L",i.tagName="",i.state=f.SCRIPT;return}A(i,"onscript",i.script),i.script=""}var n=i.tags.length,h=i.tagName;i.strict||(h=h[i.looseCase]());for(var c=h;n--;){var S=i.tags[n];if(S.name!==c)N(i,"Unexpected close tag");else break}if(n<0){N(i,"Unmatched closing tag: "+i.tagName),i.textNode+="",i.state=f.TEXT;return}i.tagName=h;for(var L=i.tags.length;L-- >n;){var P=i.tag=i.tags.pop();i.tagName=i.tag.name,A(i,"onclosetag",i.tagName);var j={};for(var V in P.ns)j[V]=P.ns[V];var se=i.tags[i.tags.length-1]||i;i.opt.xmlns&&P.ns!==se.ns&&Object.keys(P.ns).forEach(function(M){var oe=P.ns[M];A(i,"onclosenamespace",{prefix:M,uri:oe})})}n===0&&(i.closedRoot=!0),i.tagName=i.attribValue=i.attribName="",i.attribList.length=0,i.state=f.TEXT}function ye(i){var n=i.entity,h=n.toLowerCase(),c,S="";return i.ENTITIES[n]?i.ENTITIES[n]:i.ENTITIES[h]?i.ENTITIES[h]:(n=h,n.charAt(0)==="#"&&(n.charAt(1)==="x"?(n=n.slice(2),c=parseInt(n,16),S=c.toString(16)):(n=n.slice(1),c=parseInt(n,10),S=c.toString(10))),n=n.replace(/^0+/,""),isNaN(c)||S.toLowerCase()!==n||c<0||c>1114111||!Ne(c)?(N(i,"Invalid character entity"),"&"+i.entity+";"):String.fromCodePoint(c))}function Ne(i){return i===9||i===10||i===13||i>=32&&i<=55295||i>=57344&&i<=65533||i>=65536&&i<=1114111}function Te(i,n){n==="<"?(i.state=f.OPEN_WAKA,i.startTagPosition=i.position):I(n)||(N(i,"Non-whitespace before first tag."),i.textNode=n,i.state=f.TEXT)}function be(i,n){var h="";return n"?(A(n,"onsgmldeclaration",n.sgmlDecl),n.sgmlDecl="",n.state=f.TEXT):(R(c)&&(n.state=f.SGML_DECL_QUOTED),n.sgmlDecl+=c);continue;case f.SGML_DECL_QUOTED:c===n.q&&(n.state=f.SGML_DECL,n.q=""),n.sgmlDecl+=c;continue;case f.DOCTYPE:c===">"?(n.state=f.TEXT,A(n,"ondoctype",n.doctype),n.doctype=!0):(n.doctype+=c,c==="["?n.state=f.DOCTYPE_DTD:R(c)&&(n.state=f.DOCTYPE_QUOTED,n.q=c));continue;case f.DOCTYPE_QUOTED:n.doctype+=c,c===n.q&&(n.q="",n.state=f.DOCTYPE);continue;case f.DOCTYPE_DTD:c==="]"?(n.doctype+=c,n.state=f.DOCTYPE):c==="<"?(n.state=f.OPEN_WAKA,n.startTagPosition=n.position):R(c)?(n.doctype+=c,n.state=f.DOCTYPE_DTD_QUOTED,n.q=c):n.doctype+=c;continue;case f.DOCTYPE_DTD_QUOTED:n.doctype+=c,c===n.q&&(n.state=f.DOCTYPE_DTD,n.q="");continue;case f.COMMENT:c==="-"?n.state=f.COMMENT_ENDING:n.comment+=c;continue;case f.COMMENT_ENDING:c==="-"?(n.state=f.COMMENT_ENDED,n.comment=pe(n.opt,n.comment),n.comment&&A(n,"oncomment",n.comment),n.comment=""):(n.comment+="-"+c,n.state=f.COMMENT);continue;case f.COMMENT_ENDED:c!==">"?(N(n,"Malformed comment"),n.comment+="--"+c,n.state=f.COMMENT):n.doctype&&n.doctype!==!0?n.state=f.DOCTYPE_DTD:n.state=f.TEXT;continue;case f.CDATA:for(var L=h-1;c&&c!=="]";)c=be(i,h++),c&&n.trackPosition&&(n.position++,c===` +`?(n.line++,n.column=0):n.column++);n.cdata+=i.substring(L,h-1),c==="]"&&(n.state=f.CDATA_ENDING);continue;case f.CDATA_ENDING:c==="]"?n.state=f.CDATA_ENDING_2:(n.cdata+="]"+c,n.state=f.CDATA);continue;case f.CDATA_ENDING_2:c===">"?(n.cdata&&A(n,"oncdata",n.cdata),A(n,"onclosecdata"),n.cdata="",n.state=f.TEXT):c==="]"?n.cdata+="]":(n.cdata+="]]"+c,n.state=f.CDATA);continue;case f.PROC_INST:c==="?"?n.state=f.PROC_INST_ENDING:I(c)?n.state=f.PROC_INST_BODY:n.procInstName+=c;continue;case f.PROC_INST_BODY:if(!n.procInstBody&&I(c))continue;c==="?"?n.state=f.PROC_INST_ENDING:n.procInstBody+=c;continue;case f.PROC_INST_ENDING:if(c===">"){let se={name:n.procInstName,body:n.procInstBody};Y(n,se),A(n,"onprocessinginstruction",se),n.procInstName=n.procInstBody="",n.state=f.TEXT}else n.procInstBody+="?"+c,n.state=f.PROC_INST_BODY;continue;case f.OPEN_TAG:F(C,c)?n.tagName+=c:(me(n),c===">"?q(n):c==="/"?n.state=f.OPEN_TAG_SLASH:(I(c)||N(n,"Invalid character in tag name"),n.state=f.ATTRIB));continue;case f.OPEN_TAG_SLASH:c===">"?(q(n,!0),le(n)):(N(n,"Forward-slash in opening tag not followed by >"),n.state=f.ATTRIB);continue;case f.ATTRIB:if(I(c))continue;c===">"?q(n):c==="/"?n.state=f.OPEN_TAG_SLASH:F(w,c)?(n.attribName=c,n.attribValue="",n.state=f.ATTRIB_NAME):N(n,"Invalid attribute name");continue;case f.ATTRIB_NAME:c==="="?n.state=f.ATTRIB_VALUE:c===">"?(N(n,"Attribute without value"),n.attribValue=n.attribName,fe(n),q(n)):I(c)?n.state=f.ATTRIB_NAME_SAW_WHITE:F(C,c)?n.attribName+=c:N(n,"Invalid attribute name");continue;case f.ATTRIB_NAME_SAW_WHITE:if(c==="=")n.state=f.ATTRIB_VALUE;else{if(I(c))continue;N(n,"Attribute without value"),n.tag.attributes[n.attribName]="",n.attribValue="",A(n,"onattribute",{name:n.attribName,value:""}),n.attribName="",c===">"?q(n):F(w,c)?(n.attribName=c,n.state=f.ATTRIB_NAME):(N(n,"Invalid attribute name"),n.state=f.ATTRIB)}continue;case f.ATTRIB_VALUE:if(I(c))continue;R(c)?(n.q=c,n.state=f.ATTRIB_VALUE_QUOTED):(n.opt.unquotedAttributeValues||z(n,"Unquoted attribute value"),n.state=f.ATTRIB_VALUE_UNQUOTED,n.attribValue=c);continue;case f.ATTRIB_VALUE_QUOTED:if(c!==n.q){c==="&"?n.state=f.ATTRIB_VALUE_ENTITY_Q:n.attribValue+=c;continue}fe(n),n.q="",n.state=f.ATTRIB_VALUE_CLOSED;continue;case f.ATTRIB_VALUE_CLOSED:I(c)?n.state=f.ATTRIB:c===">"?q(n):c==="/"?n.state=f.OPEN_TAG_SLASH:F(w,c)?(N(n,"No whitespace between attributes"),n.attribName=c,n.attribValue="",n.state=f.ATTRIB_NAME):N(n,"Invalid attribute name");continue;case f.ATTRIB_VALUE_UNQUOTED:if(!X(c)){c==="&"?n.state=f.ATTRIB_VALUE_ENTITY_U:n.attribValue+=c;continue}fe(n),c===">"?q(n):n.state=f.ATTRIB;continue;case f.CLOSE_TAG:if(n.tagName)c===">"?le(n):F(C,c)?n.tagName+=c:n.script?(n.script+=""?le(n):N(n,"Invalid characters in closing tag");continue;case f.TEXT_ENTITY:case f.ATTRIB_VALUE_ENTITY_Q:case f.ATTRIB_VALUE_ENTITY_U:var P,j;switch(n.state){case f.TEXT_ENTITY:P=f.TEXT,j="textNode";break;case f.ATTRIB_VALUE_ENTITY_Q:P=f.ATTRIB_VALUE_QUOTED,j="attribValue";break;case f.ATTRIB_VALUE_ENTITY_U:P=f.ATTRIB_VALUE_UNQUOTED,j="attribValue";break}if(c===";"){var V=ye(n);n.opt.unparsedEntities&&!Object.values(r.XML_ENTITIES).includes(V)?((n.entityCount+=1)>n.opt.maxEntityCount&&z(n,"Parsed entity count exceeds max entity count"),(n.entityDepth+=1)>n.opt.maxEntityDepth&&z(n,"Parsed entity depth exceeds max entity depth"),n.entity="",n.state=P,n.write(V),n.entityDepth-=1):(n[j]+=V,n.entity="",n.state=P)}else F(n.entity.length?E:O,c)?n.entity+=c:(N(n,"Invalid character in entity name"),n[j]+="&"+n.entity+c,n.entity="",n.state=P);continue;default:throw new Error(n,"Unknown state: "+n.state)}return n.position>=n.bufferCheckPosition&&s(n),n}String.fromCodePoint||(function(){var i=String.fromCharCode,n=Math.floor,h=function(){var c=16384,S=[],L,P,j=-1,V=arguments.length;if(!V)return"";for(var se="";++j1114111||n(M)!==M)throw RangeError("Invalid code point: "+M);M<=65535?S.push(M):(M-=65536,L=(M>>10)+55296,P=M%1024+56320,S.push(L,P)),(j+1===V||S.length>c)&&(se+=i.apply(null,S),S.length=0)}return se};Object.defineProperty?Object.defineProperty(String,"fromCodePoint",{value:h,configurable:!0,writable:!0}):String.fromCodePoint=h})()})(typeof _e=="undefined"?_e.sax={}:_e)});var tr={};jt(tr,{initKf8File:()=>Rt,initMobiFile:()=>Pt});var $=Uint8Array,ue=Uint16Array,Xt=Int32Array,Qe=new $([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),Ke=new $([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),Yt=new $([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Je=function(r,e){for(var t=new ue(31),s=0;s<31;++s)t[s]=e+=1<>1|(x&21845)<<1,ae=(ae&52428)>>2|(ae&13107)<<2,ae=(ae&61680)>>4|(ae&3855)<<4,Ge[x]=((ae&65280)>>8|(ae&255)<<8)>>1;var ae,x,we=(function(r,e,t){for(var s=r.length,o=0,l=new ue(e);o>d]=m}else for(u=new ue(s),o=0;o>15-r[o]);return u}),Ee=new $(288);for(x=0;x<144;++x)Ee[x]=8;var x;for(x=144;x<256;++x)Ee[x]=9;var x;for(x=256;x<280;++x)Ee[x]=7;var x;for(x=280;x<288;++x)Ee[x]=8;var x,rt=new $(32);for(x=0;x<32;++x)rt[x]=5;var x;var $t=we(Ee,9,1);var Zt=we(rt,5,1),ke=function(r){for(var e=r[0],t=1;te&&(e=r[t]);return e},ee=function(r,e,t){var s=e/8|0;return(r[s]|r[s+1]<<8)>>(e&7)&t},He=function(r,e){var t=e/8|0;return(r[t]|r[t+1]<<8|r[t+2]<<16)>>(e&7)},Qt=function(r){return(r+7)/8|0},Kt=function(r,e,t){return(e==null||e<0)&&(e=0),(t==null||t>r.length)&&(t=r.length),new $(r.subarray(e,t))};var Jt=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],te=function(r,e,t){var s=new Error(e||Jt[r]);if(s.code=r,Error.captureStackTrace&&Error.captureStackTrace(s,te),!t)throw s;return s},en=function(r,e,t,s){var o=r.length,l=s?s.length:0;if(!o||e.f&&!e.l)return t||new $(0);var a=!t,u=a||e.i!=2,d=e.i;a&&(t=new $(o*3));var m=function(Ie){var i=t.length;if(Ie>i){var n=new $(Math.max(i*2,Ie));n.set(t),t=n}},v=e.f||0,g=e.p||0,p=e.b||0,y=e.l,b=e.d,T=e.m,w=e.n,C=o*8;do{if(!y){v=ee(r,g,1);var O=ee(r,g+1,3);if(g+=3,O)if(O==1)y=$t,b=Zt,T=9,w=5;else if(O==2){var X=ee(r,g,31)+257,F=ee(r,g+10,15)+4,k=X+ee(r,g+5,31)+1;g+=14;for(var f=new $(k),W=new $(19),_=0;_>4;if(E<16)f[_++]=E;else{var A=0,U=0;for(E==16?(U=3+ee(r,g,3),g+=2,A=f[_-1]):E==17?(U=3+ee(r,g,7),g+=3):E==18&&(U=11+ee(r,g,127),g+=7);U--;)f[_++]=A}}var pe=f.subarray(0,X),z=f.subarray(X);T=ke(pe),w=ke(z),y=we(pe,T,1),b=we(z,w,1)}else te(1);else{var E=Qt(g)+4,I=r[E-4]|r[E-3]<<8,R=E+I;if(R>o){d&&te(0);break}u&&m(p+I),t.set(r.subarray(E,R),p),e.b=p+=I,e.p=g=R*8,e.f=v;continue}if(g>C){d&&te(0);break}}u&&m(p+131072);for(var xe=(1<>4;if(g+=A&15,g>C){d&&te(0);break}if(A||te(2),re<256)t[p++]=re;else if(re==256){me=g,y=null;break}else{var fe=re-254;if(re>264){var _=re-257,q=Qe[_];fe=ee(r,g,(1<>4;le||te(3),g+=le&15;var z=Wt[ye];if(ye>3){var q=Ke[ye];z+=He(r,g)&(1<C){d&&te(0);break}u&&m(p+131072);var Ne=p+fe;if(p>4>7||(r[0]<<8|r[1])%31)&&te(6,"invalid zlib data"),(r[1]>>5&1)==+!e&&te(6,"invalid zlib data: "+(r[1]&32?"need":"unexpected")+" dictionary"),(r[1]>>3&4)+2};function it(r,e){return en(r.subarray(nn(r,e&&e.dictionary),-4),{i:2},e&&e.out,e&&e.dictionary)}var rn=typeof TextDecoder!="undefined"&&new TextDecoder,sn=0;try{rn.decode(tn,{stream:!0}),sn=1}catch{}var ve=ze(at(),1),wt=ze(yt(),1),Et=ze(Tt(),1);var gn=Object.defineProperty,dn=(r,e,t)=>e in r?gn(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t,Z=(r,e,t)=>dn(r,typeof e!="symbol"?e+"":e,t);function vn(r){return r.charCodeAt(0)===65279?r.slice(1):r}var bt={.1:{explicitCharkey:!1,trim:!0,normalize:!0,normalizeTags:!1,attrkey:"@",charkey:"#",explicitArray:!1,ignoreAttrs:!1,mergeAttrs:!1,explicitRoot:!1,validator:null,xmlns:!1,explicitChildren:!1,childkey:"@@",charsAsChildren:!1,includeWhiteChars:!1,async:!1,strict:!0,attrNameProcessors:null,attrValueProcessors:null,tagNameProcessors:null,valueProcessors:null,emptyTag:""},.2:{explicitCharkey:!1,trim:!1,normalize:!1,normalizeTags:!1,attrkey:"$",charkey:"_",explicitArray:!0,ignoreAttrs:!1,mergeAttrs:!1,explicitRoot:!0,validator:null,xmlns:!1,explicitChildren:!1,preserveChildrenOrder:!1,childkey:"$$",charsAsChildren:!1,includeWhiteChars:!1,async:!1,strict:!0,attrNameProcessors:null,attrValueProcessors:null,tagNameProcessors:null,valueProcessors:null,rootName:"root",xmldec:{version:"1.0",encoding:"UTF-8",standalone:!0},doctype:null,renderOpts:{pretty:!0,indent:" ",newline:` +`},headless:!1,chunkSize:1e4,emptyTag:"",cdata:!1}};function pn(r){return r.toLowerCase()}function mn(r){return typeof r=="object"&&r!==null&&Object.keys(r).length===0}function Fe(r,e,t){for(let s of r)e=s(e,t);return e}function de(r,e,t){let s=Object.create(null);s.value=t,s.writable=!0,s.enumerable=!0,s.configurable=!0,Object.defineProperty(r,e,s)}var Ve=class extends wt.EventEmitter{constructor(e){super(),Z(this,"options"),Z(this,"remaining",""),Z(this,"saxParser"),Z(this,"resultObject",null),Z(this,"EXPLICIT_CHARKEY"),Z(this,"errThrown",!1),Z(this,"ended",!1),Z(this,"processAsync",()=>{try{if(this.remaining.length<=this.options.chunkSize){let t=this.remaining;this.remaining="",this.saxParser=this.saxParser.write(t),this.saxParser.close()}else{let t=this.remaining.slice(0,this.options.chunkSize);this.remaining=this.remaining.slice(this.options.chunkSize),this.saxParser=this.saxParser.write(t),setTimeout(this.processAsync,0)}}catch(t){this.errThrown||(this.errThrown=!0,this.emit("error",t))}}),Z(this,"assignOrPush",(t,s,o)=>{s in t?(Array.isArray(t[s])||de(t,s,[t[s]]),t[s].push(o)):this.options.explicitArray?de(t,s,[o]):de(t,s,o)}),Z(this,"reset",()=>{this.removeAllListeners(),this.saxParser=Et.default.parser(this.options.strict,{trim:!1,normalize:!1,xmlns:this.options.xmlns}),this.errThrown=!1,this.saxParser.onerror=a=>{this.saxParser.resume(),this.errThrown||(this.errThrown=!0,this.emit("error",a))},this.saxParser.onend=()=>{this.ended||(this.ended=!0,this.emit("end",this.resultObject))},this.ended=!1,this.EXPLICIT_CHARKEY=this.options.explicitCharkey,this.resultObject=null;let t=[],s=this.options.attrkey,o=this.options.charkey;this.saxParser.onopentag=a=>{let u={};u[o]="",this.options.ignoreAttrs||Object.keys(a.attributes).forEach(d=>{!(s in u)&&!this.options.mergeAttrs&&(u[s]={});let m=this.options.attrValueProcessors?Fe(this.options.attrValueProcessors,a.attributes[d],d):a.attributes[d],v=this.options.attrNameProcessors?Fe(this.options.attrNameProcessors,d):d;this.options.mergeAttrs?this.assignOrPush(u,v,m):de(u[s],v,m)}),u["#name"]=this.options.tagNameProcessors?Fe(this.options.tagNameProcessors,a.name):a.name,this.options.xmlns&&(u[this.options.xmlnskey]={uri:a.uri,local:a.local}),t.push(u)},this.saxParser.onclosetag=()=>{let a=t.pop(),u=a["#name"];(!this.options.explicitChildren||!this.options.preserveChildrenOrder)&&delete a["#name"];let d;a.cdata===!0&&(d=a.cdata,delete a.cdata);let m=t[t.length-1],v="";if(a[o].match(/^\s*$/)&&!d?(v=a[o],delete a[o]):(this.options.trim&&(a[o]=a[o].trim()),this.options.normalize&&(a[o]=a[o].replace(/\s{2,}/g," ").trim()),a[o]=this.options.valueProcessors?Fe(this.options.valueProcessors,a[o],u):a[o],Object.keys(a).length===1&&o in a&&!this.EXPLICIT_CHARKEY&&(a=a[o])),mn(a)&&(typeof this.options.emptyTag=="function"?a=this.options.emptyTag():a=this.options.emptyTag!==""?this.options.emptyTag:v),this.options.validator){let g=`/${t.map(p=>p["#name"]).concat(u).join("/")}`;(()=>{try{a=this.options.validator(g,m&&m[u],a)}catch(p){this.emit("error",p)}})()}if(this.options.explicitChildren&&!this.options.mergeAttrs&&typeof a=="object")if(this.options.preserveChildrenOrder){if(m){m[this.options.childkey]=m[this.options.childkey]||[];let g={};Object.keys(a).forEach(p=>{de(g,p,a[p])}),m[this.options.childkey].push(g),delete a["#name"],Object.keys(a).length===1&&o in a&&!this.EXPLICIT_CHARKEY&&(a=a[o])}}else{let g={};this.options.attrkey in a&&(g[this.options.attrkey]=a[this.options.attrkey],delete a[this.options.attrkey]),!this.options.charsAsChildren&&this.options.charkey in a&&(g[this.options.charkey]=a[this.options.charkey],delete a[this.options.charkey]),Object.getOwnPropertyNames(a).length>0&&(g[this.options.childkey]=a),a=g}if(t.length>0)this.assignOrPush(m,u,a);else{if(this.options.explicitRoot){let g=a,p={};de(p,u,g),a=p}this.resultObject=a,this.ended=!0,this.emit("end",this.resultObject)}};let l=a=>{let u=t[t.length-1];if(u&&(u[o]+=a,this.options.explicitChildren&&this.options.preserveChildrenOrder&&this.options.charsAsChildren&&(this.options.includeWhiteChars||a.replace(/\\n/g,"").trim()!==""))){u[this.options.childkey]=u[this.options.childkey]||[];let d={"#name":"__text__"};d[o]=a,this.options.normalize&&(d[o]=d[o].replace(/\s{2,}/g," ").trim()),u[this.options.childkey].push(d)}return u};this.saxParser.ontext=l,this.saxParser.oncdata=a=>{let u=l(a);u&&(u.cdata=!0)}}),Z(this,"parseString",(t,s)=>{s&&typeof s=="function"&&(this.on("end",o=>{this.reset(),s(null,o)}),this.on("error",o=>{this.reset(),s(o)}));try{return t=t.toString(),t.trim()===""?(this.emit("end",null),!0):(t=vn(t),this.options.async?(this.remaining=t,setTimeout(this.processAsync,0),this.saxParser):this.saxParser.write(t).close())}catch(o){if(!this.errThrown&&!this.ended)this.emit("error",o),this.errThrown=!0;else if(this.ended)throw o}}),Z(this,"parseStringPromise",t=>new Promise((s,o)=>{this.parseString(t,(l,a)=>{l?o(l):s(a)})})),this.options={},Object.keys(bt["0.2"]).forEach(t=>{this.options[t]=bt["0.2"][t]}),e&&Object.keys(e).forEach(t=>{this.options[t]=e[t]}),this.options.xmlns&&(this.options.xmlnskey=`${this.options.attrkey}ns`),this.options.normalizeTags&&(this.options.tagNameProcessors||(this.options.tagNameProcessors=[]),this.options.tagNameProcessors.unshift(pn)),this.reset()}};function yn(r,e){let t={};return typeof e=="object"&&(t=e),new Ve(t).parseStringPromise(r)}async function At(r,e={}){return await yn(r,e)}var Tn={"<":"<",">":">","&":"&",""":'"',"'":"'"};function Ct(r){return r.includes("&")?r.replace(/&(#x[\dA-Fa-f]+|#\d+|[a-zA-Z]+);/g,(e,t)=>t.startsWith("#x")?String.fromCodePoint(Number.parseInt(t.slice(2),16)):t.startsWith("#")?String.fromCodePoint(Number.parseInt(t.slice(1),10)):Tn[e]||e):r}var xt={XML:"application/xml",XHTML:"application/xhtml+xml",HTML:"text/html",CSS:"text/css",SVG:"image/svg+xml"},bn={ffd8ff:"image/jpeg","89504e47":"image/png",47494638:"image/gif","424d":"image/bmp","3c737667":"image/svg+xml","00000018":"video/mp4","00000020":"video/mp4","1a45dfa3":"video/mkv","1f43b675":"video/webm",494433:"audio/mp3",52494646:"audio/wav","4f676753":"audio/ogg","00010000":"font/ttf",74727565:"font/ttf","4f54544f":"font/otf","774f4646":"font/woff","774f4632":"font/woff2","504c":"font/eot"};function wn(r){let e=r.slice(0,12),t=Array.from(e).map(s=>s.toString(16).padStart(2,"0")).join("");for(let[s,o]of Object.entries(bn))if(t.startsWith(s))return o;return"unknown"}function Pe(r,e,t,s){return URL.createObjectURL(new Blob([r],{type:e}))}var En={1252:"windows-1252",65001:"utf-8"},An={1:["ar","ar-SA","ar-IQ","ar-EG","ar-LY","ar-DZ","ar-MA","ar-TN","ar-OM","ar-YE","ar-SY","ar-JO","ar-LB","ar-KW","ar-AE","ar-BH","ar-QA"],2:["bg"],3:["ca"],4:["zh","zh-TW","zh-CN","zh-HK","zh-SG"],5:["cs"],6:["da"],7:["de","de-DE","de-CH","de-AT","de-LU","de-LI"],8:["el"],9:["en","en-US","en-GB","en-AU","en-CA","en-NZ","en-IE","en-ZA","en-JM",null,"en-BZ","en-TT","en-ZW","en-PH"],10:["es","es-ES","es-MX",null,"es-GT","es-CR","es-PA","es-DO","es-VE","es-CO","es-PE","es-AR","es-EC","es-CL","es-UY","es-PY","es-BO","es-SV","es-HN","es-NI","es-PR"],11:["fi"],12:["fr","fr-FR","fr-BE","fr-CA","fr-CH","fr-LU","fr-MC"],13:["he"],14:["hu"],15:["is"],16:["it","it-IT","it-CH"],17:["ja"],18:["ko"],19:["nl","nl-NL","nl-BE"],20:["no","nb","nn"],21:["pl"],22:["pt","pt-BR","pt-PT"],23:["rm"],24:["ro"],25:["ru"],26:["hr",null,"sr"],27:["sk"],28:["sq"],29:["sv","sv-SE","sv-FI"],30:["th"],31:["tr"],32:["ur"],33:["id"],34:["uk"],35:["be"],36:["sl"],37:["et"],38:["lv"],39:["lt"],41:["fa"],42:["vi"],43:["hy"],44:["az"],45:["eu"],46:["hsb"],47:["mk"],48:["st"],49:["ts"],50:["tn"],52:["xh"],53:["zu"],54:["af"],55:["ka"],56:["fo"],57:["hi"],58:["mt"],59:["se"],62:["ms"],63:["kk"],65:["sw"],67:["uz",null,"uz-UZ"],68:["tt"],69:["bn"],70:["pa"],71:["gu"],72:["or"],73:["ta"],74:["te"],75:["kn"],76:["ml"],77:["as"],78:["mr"],79:["sa"],82:["cy","cy-GB"],83:["gl","gl-ES"],87:["kok"],97:["ne"],98:["fy"]},Cn={name:[0,32,"string"],type:[60,4,"string"],creator:[64,4,"string"],numRecords:[76,2,"uint"]},xn={compression:[0,2,"uint"],numTextRecords:[8,2,"uint"],recordSize:[10,2,"uint"],encryption:[12,2,"uint"]},Nn={magic:[16,4,"string"],length:[20,4,"uint"],type:[24,4,"uint"],encoding:[28,4,"uint"],uid:[32,4,"uint"],version:[36,4,"uint"],titleOffset:[84,4,"uint"],titleLength:[88,4,"uint"],localeRegion:[94,1,"uint"],localeLanguage:[95,1,"uint"],resourceStart:[108,4,"uint"],huffcdic:[112,4,"uint"],numHuffcdic:[116,4,"uint"],exthFlag:[128,4,"uint"],trailingFlags:[240,4,"uint"],indx:[244,4,"uint"]},In={resourceStart:[108,4,"uint"],fdst:[192,4,"uint"],numFdst:[196,4,"uint"],frag:[248,4,"uint"],skel:[252,4,"uint"],guide:[260,4,"uint"]},Sn={magic:[0,4,"string"],length:[4,4,"uint"],count:[8,4,"uint"]},Nt={magic:[0,4,"string"],length:[4,4,"uint"],type:[8,4,"uint"],idxt:[20,4,"uint"],numRecords:[24,4,"uint"],encoding:[28,4,"uint"],language:[32,4,"uint"],total:[36,4,"uint"],ordt:[40,4,"uint"],ligt:[44,4,"uint"],numLigt:[48,4,"uint"],numCncx:[52,4,"uint"]},Dn={magic:[0,4,"string"],length:[4,4,"uint"],numControlBytes:[8,4,"uint"]},On={magic:[0,4,"string"],offset1:[8,4,"uint"],offset2:[12,4,"uint"]},_n={magic:[0,4,"string"],length:[4,4,"uint"],numEntries:[8,4,"uint"],codeLength:[12,4,"uint"]},Fn={magic:[0,4,"string"],numEntries:[8,4,"uint"]},Ln={flags:[8,4,"uint"],dataStart:[12,4,"uint"],keyLength:[16,4,"uint"],keyStart:[20,4,"uint"]};function _t(r){var t;let e="";return e=(t=r.name)!=null?t:"",e}function Rn(r){return r.buffer.slice(r.byteOffset,r.byteOffset+r.byteLength)}async function Ft(r){return r instanceof Uint8Array?Rn(r):await r.arrayBuffer()}var Pn=new TextDecoder,Ae=r=>Pn.decode(r);function G(r){let e=r.byteLength,t=e===4?"getUint32":e===2?"getUint16":"getUint8";return new DataView(r)[t](0)}function K(r,e){let t={};for(let s in r){let[o,l,a]=r[s];t[s]=a==="string"?Ae(e.slice(o,o+l)):G(e.slice(o,o+l))}return t}function Ce(r){let e=r.reduce((o,l)=>o+l.length,0),t=new r[0].constructor(e),s=0;for(let o of r)t.set(o,s),s+=o.length;return t}var qe=r=>new TextDecoder(En[r]);function Le(r,e=0){let t=0,s=0;for(let o of r.subarray(e,e+4))if(t=t<<7|(o&127)>>>0,s++,o&128)break;return{value:t,length:s}}function Bn(r){let e=0;for(let t of r.subarray(-4))t&128&&(e=0),e=e<<7|t&127;return e}function Lt(r){let e=0;for(;r>0;r=r>>1)(r&1)===1&&e++;return e}function Un(r){let e=0;for(;(r&1)===0;)r=r>>1,e++;return e}function Mn(r){let e=[];for(let t=0;t>>3,a=(o&7)+3;for(let u=0;uo+g*4).map(v=>G(t.slice(v,v+4))).map(v=>[v&128,v&31,v>>>8]),u=[[0,0],...Array.from({length:32},(v,g)=>l+g*8).map(v=>[G(t.slice(v,v+4)),G(t.slice(v+4,v+8))])],d=[];for(let v=1;v{let g=new Uint8Array,p=v.byteLength*8;for(let y=0;y>>24];if(!T){for(;b>>>32-wp)break;let O=C-(b>>>32-w),[E,I]=d[O];I||(E=m(E),d[O]=[E,!0]),g=Ce([g,E])}return g};return m}function kn(r,e){var a;let t=e>>3,s=e+32,o=s>>3,l=BigInt(0);for(let u=t;u<=o;u++)l=l<>BigInt(8)-BigInt(s&7)&BigInt(0xFFFFFFFF)}var It={100:["creator","string",!0],101:["publisher","string",!1],103:["description","string",!1],104:["isbn","string",!1],105:["subject","string",!0],106:["date","string",!1],108:["contributor","string",!0],109:["rights","string",!1],110:["subjectCode","string",!0],112:["source","string",!0],113:["asin","string",!1],121:["boundary","uint",!1],122:["fixedLayout","string",!1],125:["numResources","uint",!1],126:["originalResolution","string",!1],127:["zeroGutter","string",!1],128:["zeroMargin","string",!1],129:["coverURI","string",!1],132:["regionMagnification","string",!1],201:["coverOffset","uint",!1],202:["thumbnailOffset","uint",!1],503:["title","string",!1],524:["language","string",!0],527:["pageProgressionDirection","string",!1]};function Hn(r,e){var u;let{magic:t,count:s}=K(Sn,r);if(t!=="EXTH")throw new Error("Invalid EXTH header");let o=qe(e.toString()),l={},a=12;for(let d=0;d>>1);return s=>{for(let o=0;onew Uint8Array(u.slice(12+y*4,12+y*4+4))),g=[];for(let p=0;p1){let{value:A,length:U}=Le(b,k);R.push([W,0,A,_]),k+=U}else R.push([W,1,0,_]);else R.push([W,Y>>Un(J),0,_])}let f={};for(let[W,_,J,ce]of R){let ne=[];if(_!==0)for(let Y=0;Y<_*ce;Y++){let{value:A,length:U}=Le(b,k);ne.push(A),k+=U}else{let Y=0;for(;Y{var d,m,v,g,p,y,b,T;return{index:u,offset:(d=a[1])==null?void 0:d[0],size:(m=a[2])==null?void 0:m[0],label:(g=s[(v=a[3])==null?void 0:v[0]])!=null?g:"",headingLevel:(p=a[4])==null?void 0:p[0],pos:a[6],parent:(y=a[21])==null?void 0:y[0],firstChild:(b=a[22])==null?void 0:b[0],lastChild:(T=a[23])==null?void 0:T[0]}}),l=a=>(a.firstChild==null||(a.children=o.filter(u=>u.parent===a.index).map(l)),a);return o.filter(a=>a.headingLevel===0).map(l)}var Xn=/<\s*(?:mbp:)?pagebreak[^>]*>/gi;function St(r=0,e=0){return`kindle:pos:fid:${r.toString(32).toUpperCase().padStart(4,"0")}:off:${e.toString(32).toUpperCase().padStart(10,"0")}`}var Yn=/\s(id|name|aid)\s*=\s*['"]([^'"]*)['"]/i;function Dt(r){let e=r.match(Yn);if(!e)return"";let[,t,s]=e;return`[${t}="${s}"]`}var qn=/kindle:pos:fid:(\w+):off:(\w+)/;function Wn(r){let[e,t]=r.match(qn).slice(1);return{fid:Number.parseInt(e,32),off:Number.parseInt(t,32)}}var Ot=/kindle:(flow|embed):(\w+)(?:\?mime=(\w+\/[-+.\w]+))?/,$n=Object.defineProperty,Zn=(r,e,t)=>e in r?$n(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t,H=(r,e,t)=>Zn(r,typeof e!="symbol"?e+"":e,t),Be=class{constructor(e,t){var s;if(H(this,"fileArrayBuffer"),H(this,"recordsOffset"),H(this,"recordsMagic"),H(this,"start",0),H(this,"pdbHeader"),H(this,"mobiHeader"),H(this,"palmdocHeader"),H(this,"kf8Header"),H(this,"exth"),H(this,"isKf8",!1),H(this,"resourceStart"),H(this,"decoder"),H(this,"encoder"),H(this,"removeTrailingEntries"),H(this,"decompress"),this.fileArrayBuffer=e,this.parsePdbHeader(),this.parseFirstRecord(this.loadRecord(0)),this.resourceStart=this.mobiHeader.resourceStart,!this.isKf8){let o=(s=this.exth.boundary)!=null?s:4294967295;if(o<4294967295){try{this.parseFirstRecord(this.loadRecord(o)),this.start=o,this.isKf8=!0}catch{console.warn("Failed to parse kf8 header.")}t.endsWith(".mobi")&&console.warn(`File "${t}" is a compatible file. Please change the file extension to .azw3 to make it parse correctly.`)}}this.setup()}decode(e){return this.decoder.decode(e)}encode(e){return this.encoder.encode(e)}pdbLoadRecord(e){var o;let[t,s]=(o=this.recordsOffset[e])!=null?o:[0,0];return this.fileArrayBuffer.slice(t,s)}loadRecord(e){return this.pdbLoadRecord(this.start+e)}loadMagic(e){return this.recordsMagic[this.start+e]}loadTextBuffer(e){return this.decompress(this.removeTrailingEntries(new Uint8Array(this.loadRecord(e+1))))}loadResource(e){let t=this.pdbLoadRecord(this.resourceStart+e),s=Ae(t.slice(0,4)),o;return s==="FONT"?o=jn(t):s==="VIDE"||s==="AUDI"?o=new Uint8Array(t.slice(12)):o=new Uint8Array(t),{type:wn(o),raw:o}}getNCX(){let e=this.mobiHeader.indx;if(e<4294967295)return Vn(e,this.loadRecord.bind(this))}getMetadata(){var s,o,l,a,u,d,m,v,g,p,y;let e=this.mobiHeader,t=this.exth;return{identifier:this.mobiHeader.uid.toString(),title:(t==null?void 0:t.title)||e.title,author:(o=(s=t==null?void 0:t.creator)==null?void 0:s.map(Ct))!=null?o:[],publisher:(l=t==null?void 0:t.publisher)!=null?l:"",language:(u=(a=t==null?void 0:t.language)==null?void 0:a[0])!=null?u:e.language,published:(d=t==null?void 0:t.date)!=null?d:"",description:(m=t==null?void 0:t.description)!=null?m:"",subject:(g=(v=t==null?void 0:t.subject)==null?void 0:v.map(Ct))!=null?g:[],rights:(p=t==null?void 0:t.rights)!=null?p:"",contributor:(y=t==null?void 0:t.contributor)!=null?y:[]}}getCoverImage(){var l,a;let e=this.exth,t=Number((l=e.coverOffset)!=null?l:4294967295),s=Number((a=e.thumbnailOffset)!=null?a:4294967295),o=t<4294967295?t:s<4294967295?s:void 0;if(o)return this.loadResource(o)}parsePdbHeader(){let e=K(Cn,this.fileArrayBuffer.slice(0,78));e.name=e.name.replace(/\0.*$/,""),this.pdbHeader=e;let t=this.fileArrayBuffer.slice(78,78+e.numRecords*8),s=Array.from({length:e.numRecords},(o,l)=>G(t.slice(l*8,l*8+4)));this.recordsOffset=s.map((o,l)=>[o,s[l+1]]),this.recordsMagic=s.map(o=>Ae(this.fileArrayBuffer.slice(o,o+4)))}parseFirstRecord(e){var m,v,g;this.palmdocHeader=K(xn,e.slice(0,16));let t=K(Nn,e);if(t.magic!=="MOBI")throw new Error("Missing MOBI header");let{titleOffset:s,titleLength:o,localeLanguage:l,localeRegion:a}=t,u=(m=An[l.toString()])!=null?m:[],d={title:Ae(e.slice(s,s+o)),language:(g=(v=u[a>>2])!=null?v:u[0])!=null?g:"unknown"};this.mobiHeader=Object.assign(t,d),this.kf8Header=t.version>=8?K(In,e):void 0,this.isKf8=t.version>=8,this.exth=t.exthFlag&64?Hn(e.slice(t.length+16),t.encoding):void 0}setup(){this.decoder=qe(this.mobiHeader.encoding.toString()),this.encoder=new TextEncoder;let e=this.palmdocHeader.compression;if(e===1)this.decompress=s=>s;else if(e===2)this.decompress=Mn;else if(e===17480)this.decompress=zn(this.mobiHeader,this.loadRecord.bind(this));else throw new Error("Unsupported compression");let t=this.mobiHeader.trailingFlags;this.removeTrailingEntries=Gn(t)}},Qn=Object.defineProperty,Kn=(r,e,t)=>e in r?Qn(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t,B=(r,e,t)=>Kn(r,typeof e!="symbol"?e+"":e,t);async function Rt(r,e){let t=new Xe(r,e);return await t.innerLoadFile(),await t.innerInit(),t}var Xe=class{constructor(e,t="./images"){this.file=e,B(this,"fileArrayBuffer"),B(this,"mobiFile"),B(this,"fileName",""),B(this,"fdstTable",[]),B(this,"fullRawLength",0),B(this,"skelTable",[]),B(this,"fragTable",[]),B(this,"chapters",[]),B(this,"toc",[]),B(this,"fragmentOffsets",new Map),B(this,"fragmentSelectors",new Map),B(this,"rawHead",new Uint8Array),B(this,"rawTail",new Uint8Array),B(this,"lastLoadedHead",-1),B(this,"lastLoadedTail",-1),B(this,"resourceCache",new Map),B(this,"chapterCache",new Map),B(this,"idToChapter",new Map),B(this,"resourceSaveDir","./images"),this.fileName=_t(e),this.resourceSaveDir=t}getFileInfo(){return{fileName:this.fileName}}getMetadata(){return this.mobiFile.getMetadata()}getCoverImage(){if(this.resourceCache.has("cover"))return this.resourceCache.get("cover");let e=this.mobiFile.getCoverImage(),t="";return e&&(t=Pe(e.raw,e.type,"cover",this.resourceSaveDir),this.resourceCache.set("cover",t)),t}getSpine(){return this.chapters}getToc(){return this.toc}async innerLoadFile(){this.fileArrayBuffer=await Ft(this.file),this.mobiFile=new Be(this.fileArrayBuffer,this.fileName)}async innerInit(){let e=this.mobiFile.loadRecord.bind(this.mobiFile),t=this.mobiFile.kf8Header,s=this.mobiFile.loadRecord(t.fdst),o=K(Fn,s);if(o.magic!=="FDST")throw new Error("Missing FDST record");let l=Array.from({length:o.numEntries},(p,y)=>12+y*8).map(p=>[G(s.slice(p,p+4)),G(s.slice(p+4,p+8))]);this.fdstTable=l,this.fullRawLength=l[l.length-1][1];let u=Re(t.skel,e).table.map(({name:p,tagMap:y},b)=>({index:b,name:p,numFrag:y[1][0],offset:y[6][0],length:y[6][1]}));this.skelTable=u;let d=Re(t.frag,e),m=d.table.map(({name:p,tagMap:y})=>({insertOffset:Number.parseInt(p),selector:d.cncx[y[2][0]],index:y[4][0],offset:y[6][0],length:y[6][1]}));this.fragTable=m;let v=this.skelTable.reduce((p,y,b)=>{var X,F;let T=p[p.length-1],w=(X=T==null?void 0:T.fragEnd)!=null?X:0,C=w+y.numFrag,O=this.fragTable.slice(w,C),E=y.length+O.reduce((k,f)=>k+f.length,0),I=((F=T==null?void 0:T.totalLength)!=null?F:0)+E,R={id:b.toString(),skel:y,frags:O,fragEnd:C,length:E,totalLength:I};return this.idToChapter.set(b,R),p.push(R),p},[]);this.chapters=v;let g=this.mobiFile.getNCX();if(g){let p=({label:y,pos:b,children:T})=>{let[w,C]=b,O=St(w,C),E=this.fragmentOffsets.get(w);return E?E.push(C):this.fragmentOffsets.set(w,[C]),{label:y,href:O,children:T==null?void 0:T.map(p)}};this.toc=g.map(p)}}getGuide(){let e=this.mobiFile.kf8Header.guide;if(e<4294967295){let t=this.mobiFile.loadRecord.bind(this.mobiFile),{table:s,cncx:o}=Re(e,t);return s.map(({name:l,tagMap:a})=>{var u,d,m,v;return{label:(u=o[a[1][0]])!=null?u:"",type:l==null?void 0:l.split(/\s/),href:St((v=(d=a[6])==null?void 0:d[0])!=null?v:(m=a[3])==null?void 0:m[0])}})}}loadRaw(e,t){let s=t-this.rawHead.length,o=this.fullRawLength===0?1/0:this.fullRawLength-this.rawTail.length-e;if(s<0||se;){this.lastLoadedTail++;let a=this.mobiFile.palmdocHeader.numTextRecords-1-this.lastLoadedTail,u=this.mobiFile.loadTextBuffer(a);this.rawTail=Ce([u,this.rawTail])}let l=this.fullRawLength-this.rawTail.length;return this.rawTail.slice(e-l,t-l)}loadText(e){let{skel:t,frags:s,length:o}=e,l=this.loadRaw(t.offset,t.offset+o),a=l.slice(0,t.length);for(let u of s){let d=u.insertOffset-t.offset,m=t.length+u.offset,v=l.slice(m,m+u.length);a=Ce([a.slice(0,d),v,a.slice(d)]);let g=this.fragmentOffsets.get(u.index);if(g)for(let p of g){let y=this.mobiFile.decode(v.buffer).slice(p),b=Dt(y);b&&this.cacheFragmentSelector(u.index,p,b)}}return this.mobiFile.decode(a.buffer)}loadChapter(e){let t=Number.parseInt(e);if(Number.isNaN(t))return;if(this.chapterCache.has(t))return this.chapterCache.get(t);let s=this.idToChapter.get(t);if(s){let o=this.replace(this.loadText(s));return this.chapterCache.set(t,o),o}}cacheFragmentSelector(e,t,s){let o=this.fragmentSelectors.get(e);if(o)o.set(t,s);else{let l=new Map;this.fragmentSelectors.set(e,l),l.set(t,s)}}loadFlow(e){if(e<4294967295)return this.loadRaw(this.fdstTable[e][0],this.fdstTable[e][1])}resolveHref(e){var b;if(/^(?!blob|kindle)\w+:/i.test(e))return;let{fid:t,off:s}=Wn(e),o=this.chapters.find(T=>T.frags.some(w=>w.index===t));if(!o)return;let l=o.id,a=(b=this.fragmentSelectors.get(t))==null?void 0:b.get(s);if(a)return{id:l,selector:a};let{skel:u,frags:d}=o,m=d.find(T=>T.index===t),v=u.offset+u.length+m.offset,g=this.loadRaw(v,v+m.length),p=this.mobiFile.decode(g.buffer).slice(s),y=Dt(p);return this.cacheFragmentSelector(t,s,y),{id:l,selector:y}}replaceResources(e){return e.replace(new RegExp(Ot,"gi"),(t,s,o,l)=>{if(this.resourceCache.has(t))return this.resourceCache.get(t);let a=s==="flow"?this.loadFlow(Number.parseInt(o)):this.mobiFile.loadResource(Number.parseInt(o,36)-1).raw,u="";if(l===xt.CSS||l===xt.SVG){let m=this.mobiFile.decode(a==null?void 0:a.buffer);u=this.replaceResources(m)}else u=a;let d=Pe(u,l,o,this.resourceSaveDir);return this.resourceCache.set(t,d),d})}replace(e){var u;let t=[],o=(u=e.match(/]*>([\s\S]*)<\/head>/i)[1].match(/]*>/gi))!=null?u:[];for(let d of o){let m=d.match(/href="([^"]*)"/i)[1],v=d.match(Ot)[2],g=this.replaceResources(m);t.push({id:v,href:g})}let l=e.match(/]*>([\s\S]*)<\/body>/i)[1];return{html:this.replaceResources(l),css:t}}destroy(){this.resourceCache.forEach(e=>{URL.revokeObjectURL(e)})}},Jn=Object.defineProperty,er=(r,e,t)=>e in r?Jn(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t,Q=(r,e,t)=>er(r,typeof e!="symbol"?e+"":e,t);async function Pt(r,e){let t=new Ye(r,e);return await t.innerLoadFile(),await t.innerInit(),t}var Ye=class{constructor(e,t="./images"){this.file=e,Q(this,"fileArrayBuffer"),Q(this,"mobiFile"),Q(this,"fileName",""),Q(this,"chapters",[]),Q(this,"idToChapter",new Map),Q(this,"toc",[]),Q(this,"resourceSaveDir","./images"),Q(this,"chapterCache",new Map),Q(this,"resourceCache",new Map),Q(this,"recindexReg",/recindex=["']?(\d+)["']?/),Q(this,"mediarecindexReg",/mediarecindex=["']?(\d+)["']?/),Q(this,"fileposReg",/filepos=["']?(\d+)["']?/),this.fileName=_t(e),this.resourceSaveDir=t}getFileInfo(){return{fileName:this.fileName}}getSpine(){return this.chapters}loadChapter(e){let t=Number.parseInt(e);if(Number.isNaN(t))return;if(this.chapterCache.has(t))return this.chapterCache.get(t);let s=this.idToChapter.get(t);if(!s)return;let o=this.replace(s.text);return this.chapterCache.set(t,o),o}getToc(){return this.toc}getCoverImage(){if(this.resourceCache.has("cover"))return this.resourceCache.get("cover");let e=this.mobiFile.getCoverImage(),t="";return e&&(t=Pe(e.raw,e.type,"cover",this.resourceSaveDir),this.resourceCache.set("cover",t)),t}getMetadata(){return this.mobiFile.getMetadata()}async innerLoadFile(){this.fileArrayBuffer=await Ft(this.file),this.mobiFile=new Be(this.fileArrayBuffer,this.fileName)}async innerInit(){var b;let{palmdocHeader:e}=this.mobiFile,t=[];for(let T=0;TString.fromCharCode(T)).join(""),l=[],a=new Map,u=0,d=Array.from(o.matchAll(Xn));d.unshift({index:0,input:"",groups:void 0,0:""});for(let T=0;Tk.charCodeAt(0)),X=this.mobiFile.decode(R.buffer),F={id:String(u),text:X,start:C,end:E,size:R.length};l.push(F),a.set(u,F),u++}let m=l[l.length-1].text;l[l.length-1].text=m.slice(0,m.indexOf(""));let v=l[0].text,g=v.indexOf("");l[0].text=v.slice(g+6),this.chapters=l,this.idToChapter=a;let p=v.slice(0,g),y=this.findTocChapter(p);if(y){let T=`${y.text.replace(/filepos=(\d+)/gi,'filepos="$1"')}`,w=await At(T,{preserveChildrenOrder:!0,explicitChildren:!0,childkey:"children"}),C=[];this.parseNavMap(w.wrapper.children,C),this.toc=C}}findTocChapter(e){var a,u;let t=//g,s=e.match(t),o=/type="(.+?)"/,l=/filepos=(.*)/;if(s)for(let d of s){let m=(a=d.match(o))==null?void 0:a[1].trim(),v=(u=d.match(l))==null?void 0:u[1].trim();if(m==="toc"&&v){let g=Number.parseInt(v,10);return this.chapters.find(y=>y.end>g)}}}parseNavMap(e,t){for(let s of e){let o=s["#name"];if(o==="p"||o==="blockquote"){let l={label:"",href:""};if(s.a){let a=s.a[0],u=a._,d=Number(a.$.filepos);l={label:u,href:`filepos:${d}`},t.push(l)}(s.p||s.blockquote)&&(l.children=[],this.parseNavMap(s.children,l.children))}}}loadResource(e){if(this.resourceCache.has(String(e)))return this.resourceCache.get(String(e));let{type:t,raw:s}=this.mobiFile.loadResource(e-1),o=Pe(s,t,String(e),this.resourceSaveDir);return this.resourceCache.set(String(e),o),o}replace(e){return e=e.replace(/]*>/g,t=>{let s=t.match(this.recindexReg)[1],o=this.loadResource(Number.parseInt(s));return t.replace(this.recindexReg,`src="${o}"`)}),e=e.replace(/<(video|audio)[^>]*>/g,t=>{var a;let s=t.match(this.recindexReg)[1],o=this.loadResource(Number.parseInt(s));t=t.replace(this.mediarecindexReg,`src="${o}"`);let l=(a=t.match(this.recindexReg))==null?void 0:a[1];if(l){let u=this.loadResource(Number.parseInt(l));t=t.replace(this.recindexReg,`poster="${u}"`)}return t}),e=e.replace(/]*>/g,t=>{let s=t.match(this.fileposReg);if(!s)return t;let o=s[1];return t.replace(this.fileposReg,`href="filepos:${o}"`)}),{html:e,css:[]}}resolveHref(e){let t=e.match(/filepos:(\d+)/);if(!t)return;let s=t[1],o=Number(s),l=this.chapters.find(a=>a.end>o);if(l)return{id:l.id,selector:`[id="filepos:${s}"]`}}destroy(){this.resourceCache.forEach(e=>{URL.revokeObjectURL(e)}),this.resourceCache.clear()}};return Vt(tr);})(); +/*! Bundled license information: + +sax/lib/sax.js: + (*! http://mths.be/fromcodepoint v0.1.0 by @mathias *) +*/ diff --git a/tools/books-test.js b/tools/books-test.js index 5e35c30..f9e39c3 100644 --- a/tools/books-test.js +++ b/tools/books-test.js @@ -15,7 +15,11 @@ const hub = http.createServer((req, res) => users.runAs(req.headers['x-user'] == r = await up('d.docx', docx); assert.equal(r.status, 200); assert.equal((await up('fake.pdf', Buffer.from('not a pdf'))).status, 400, 'a file must look like what its name says'); assert.equal((await up('fake.epub', pdf)).status, 400); assert.equal((await up('x.exe', epub)).status, 400); assert.equal((await up('plain.docx', epub)).status, 400, 'an EPUB is not a DOCX'); - assert.equal((await call('/list')).status, 200); assert.equal((await (await call('/list')).json()).length, 3); + const mobi = fs.readFileSync(path.join(fx, 'test.mobi')), drm = fs.readFileSync(path.join(fx, 'drm.mobi')); + r = await up('k.azw3', mobi); assert.equal(r.status, 200); assert.equal((await r.json()).ext, 'azw3', 'DRM-free Kindle files are accepted'); + r = await up('locked.mobi', drm); assert.equal(r.status, 400); assert.match((await r.json()).error, /DRM/, 'books with DRM are refused with a clear reason'); + assert.equal((await up('fake.mobi', pdf)).status, 400, 'a MOBI must look like one'); + assert.equal((await call('/list')).status, 200); assert.equal((await (await call('/list')).json()).length, 4); assert.equal((await (await call('/list', { headers: { 'x-user': 'b' } })).json()).length, 0, 'each person has their own shelf'); assert.equal((await call('/book/' + e.id + '/file', { headers: { 'x-user': 'b' } })).status, 404, "nobody can open another person's book"); r = await call('/book/' + p.id + '/file', { headers: { range: 'bytes=0-4' } }); assert.equal(r.status, 206); assert.equal(await r.text(), '%PDF-'); assert.equal(r.headers.get('content-range'), `bytes 0-4/${pdf.length}`); @@ -34,7 +38,7 @@ const hub = http.createServer((req, res) => users.runAs(req.headers['x-user'] == assert.deepEqual(ns.map(n => n.id).sort(), ['n1', 'n2'], 'two devices add notes without losing each other'); assert.equal(ns.find(n => n.id === 'n1').text, 'hello', 'an older edit does not win'); ns = await note([{ id: 'n1', t: 20, del: true, loc: 'y' }]); assert.deepEqual(ns.map(n => n.id), ['n2'], 'deleted notes stay deleted'); assert.equal((await (await call('/book/' + e.id + '/notes')).json()).length, 1); - assert.equal((await call('/book/' + e.id, { method: 'DELETE' })).status, 200); assert.equal((await (await call('/list')).json()).length, 2); + assert.equal((await call('/book/' + e.id, { method: 'DELETE' })).status, 200); assert.equal((await (await call('/list')).json()).length, 3); assert.equal((await call('/book/' + e.id + '/file')).status, 404); assert.equal((await call('/nope')).status, 404); console.log('books test ok'); diff --git a/tools/mkbooks.py b/tools/mkbooks.py index 4cad5aa..9ebb880 100644 --- a/tools/mkbooks.py +++ b/tools/mkbooks.py @@ -22,3 +22,33 @@ data = '%PDF-1.4\n'; offs = [] for i, o in enumerate(objs): offs.append(len(data)); data += f'{i+1} 0 obj\n{o}\nendobj\n' x = len(data); data += f'xref\n0 {len(objs)+1}\n0000000000 65535 f \n' + ''.join(f'{o:010d} 00000 n \n' for o in offs) + f'trailer\n<< /Size {len(objs)+1} /Root 1 0 R >>\nstartxref\n{x}\n%%EOF\n' open(os.path.join(out, 'test.pdf'), 'w').write(data) + +# minimal MOBI 6 (uncompressed text, no images), enough to open in a reader. Set DRM=1 in the environment to mark it encrypted. +import struct +def mkmobi(path, drm=False): + html = 'Mobi Rabbits

Chapter One

' + ''.join('

The rabbit hops through the clover field number %d, nibbling as it goes.

' % i for i in range(30)) + '

Chapter Two

' + ''.join('

Carrots are the second chapter topic, paragraph %d.

' % i for i in range(30)) + '' + text = html.encode('utf8'); recs = [text[i:i+4096] for i in range(0, len(text), 4096)] + title = b'Mobi Rabbits'; hdrlen = 232 + exth = b'' + def item(t, v): return struct.pack('>II', t, 8 + len(v)) + v + body = item(100, b'M. Author') + item(503, title); exth = b'EXTH' + struct.pack('>II', 12 + len(body), 2) + body + exth += b'\0' * (-len(exth) % 4) + r0 = struct.pack('>HHIHHHH', 1, 0, len(text), len(recs), 4096, 2 if drm else 0, 0) + mobi = bytearray(hdrlen) + mobi[0:4] = b'MOBI'; struct.pack_into('>I', mobi, 4, hdrlen); struct.pack_into('>I', mobi, 8, 2); struct.pack_into('>I', mobi, 12, 65001); struct.pack_into('>I', mobi, 16, 1234); struct.pack_into('>I', mobi, 20, 6) + for off in range(24, 64, 4): struct.pack_into('>I', mobi, off, 0xFFFFFFFF) + name_off = 16 + hdrlen + len(exth) + struct.pack_into('>II', mobi, 68 - 16, name_off, len(title)); struct.pack_into('>I', mobi, 72 - 16 + 4, 9) + struct.pack_into('>I', mobi, 0x5C - 16 + 0, 0) + struct.pack_into('>I', mobi, 0x6C - 16, 0xFFFFFFFF); struct.pack_into('>I', mobi, 0x70 - 16, 0xFFFFFFFF) + struct.pack_into('>I', mobi, 0x80 - 16, 0x40) + for off in (0x90 - 16, 0x94 - 16, 0x98 - 16, 0xA0 - 16): struct.pack_into('>I', mobi, off, 0xFFFFFFFF) + struct.pack_into('>I', mobi, 0xE0 - 16, 0xFFFFFFFF); struct.pack_into('>H', mobi, 0xF2 - 16, 0) + rec0 = r0 + bytes(mobi) + exth + title + b'\0\0' + allrecs = [rec0] + recs; n = len(allrecs); off = 78 + 8 * n + 2; offs = [] + for r in allrecs: offs.append(off); off += len(r) + hdr = b'MobiRabbits'.ljust(32, b'\0') + struct.pack('>HHIIIIII', 0, 0, 0, 0, 0, 0, 0, 0) + b'BOOKMOBI' + struct.pack('>IIH', 1234, 0, n) + hdr = hdr[:78 - 0] if len(hdr) >= 78 else hdr.ljust(78, b'\0') + lst = b''.join(struct.pack('>IBBBB', o, 0, 0, 0, i & 255) for i, o in enumerate(offs)) + open(path, 'wb').write(hdr + lst + b'\0\0' + b''.join(allrecs)) +mkmobi(os.path.join(out, 'test.mobi')); mkmobi(os.path.join(out, 'drm.mobi'), True)