Created
July 25, 2026 13:26
-
-
Save marcus-at-localhost/80ea8b7966534966637716bf81792d27 to your computer and use it in GitHub Desktop.
open dev tools on a flipbookhtml5 thingie and paste this code into the console! Don't do it if you don't know what the code is doing! It's a security ris to copy and paste code from the internet in your browser! this is relying on `fliphtml5_pages` global variable to count the pages and this gets around CSP isuues (3rd party pdf.js script is not…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Script d'extraction BookPreview/FlipPDFPlusPro vers PDF | |
| // À exécuter dans la console du navigateur | |
| // Builds PDF from JPEG bytes locally — no external libs, no CSP issues. | |
| // based on https://github.com/arthur-mdn/ExtractJSFlipPDFPlusProToPDF/blob/main/script.js | |
| const debug = false; | |
| (async function () { | |
| console.log('Starting extraction...'); | |
| // URL auto-detection | |
| const resources = performance.getEntriesByType('resource'); | |
| if (debug) console.log(resources); | |
| const pageRequests = resources.filter(r => | |
| r.initiatorType.includes('img') && | |
| r.name.includes('/page/') && | |
| (r.name.includes('.jpg') || r.name.endsWith('.png') || r.name.endsWith('.jpeg')) | |
| ); | |
| if (debug) console.log(pageRequests); | |
| if (pageRequests.length === 0) { | |
| console.error('❌ No page images found, please check the URL or the site structure.'); | |
| return; | |
| } | |
| // Extract base URL and format | |
| const firstPageUrl = pageRequests[0].name; | |
| const match = firstPageUrl.match(/(.*\/page\/)(\d+)\.(jpg|png)/); | |
| if (!match) { | |
| console.error('❌ Could not parse the page URL format.'); | |
| return; | |
| } | |
| const baseUrl = match[1]; | |
| const extension = match[3]; | |
| const queryString = firstPageUrl.includes('?') ? firstPageUrl.split('?')[1] : ''; | |
| console.log(`Base URL detected: ${baseUrl}*.<${extension}>?${queryString}`); | |
| if (typeof fliphtml5_pages === 'undefined' || !Array.isArray(fliphtml5_pages) || fliphtml5_pages.length === 0) { | |
| console.error('❌ fliphtml5_pages not found on the page.'); | |
| return; | |
| } | |
| const maxPage = fliphtml5_pages.length; | |
| const delayBetweenDownloads = 500; | |
| const fixedTimestamp = Date.now(); | |
| /** @returns {Promise<{bytes: Uint8Array, width: number, height: number}|null>} */ | |
| async function downloadImage(pageNum) { | |
| try { | |
| const url = `${baseUrl}${pageNum}.${extension}` + (queryString ? `?${queryString}` : `?t=${fixedTimestamp}`); | |
| const response = await fetch(url); | |
| if (!response.ok) { | |
| console.log(`❌ Page ${pageNum} not found (status: ${response.status})`); | |
| return null; | |
| } | |
| const blob = await response.blob(); | |
| const bitmap = await createImageBitmap(blob); | |
| const { width, height } = bitmap; | |
| let bytes; | |
| const isJpeg = blob.type === 'image/jpeg' || extension === 'jpg' || extension === 'jpeg'; | |
| if (isJpeg) { | |
| // Keep original JPEG bytes (PDF can embed them as-is) | |
| bytes = new Uint8Array(await blob.arrayBuffer()); | |
| } else { | |
| // Convert PNG/etc → JPEG via canvas | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = width; | |
| canvas.height = height; | |
| canvas.getContext('2d').drawImage(bitmap, 0, 0); | |
| const jpegBlob = await new Promise((resolve, reject) => { | |
| canvas.toBlob(b => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/jpeg', 0.95); | |
| }); | |
| bytes = new Uint8Array(await jpegBlob.arrayBuffer()); | |
| } | |
| bitmap.close(); | |
| return { bytes, width, height }; | |
| } catch (error) { | |
| console.error(`❌ Error downloading page ${pageNum}:`, error); | |
| return null; | |
| } | |
| } | |
| const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)); | |
| /** | |
| * Minimal PDF builder: one JPEG image per page, no external library. | |
| * JPEGs are embedded with /Filter /DCTDecode (no re-encode). | |
| */ | |
| function createPdfFromJpegs(pages) { | |
| const chunks = []; | |
| const enc = new TextEncoder(); | |
| let offset = 0; | |
| const xref = []; | |
| const push = (data) => { | |
| if (typeof data === 'string') data = enc.encode(data); | |
| chunks.push(data); | |
| offset += data.length; | |
| }; | |
| const startObj = (num) => { | |
| xref[num] = offset; | |
| push(`${num} 0 obj\n`); | |
| }; | |
| push('%PDF-1.4\n'); | |
| startObj(1); | |
| push('<< /Type /Catalog /Pages 2 0 R >>\nendobj\n'); | |
| const pageCount = pages.length; | |
| const pageObjNums = pages.map((_, i) => 3 + i * 3); | |
| startObj(2); | |
| push(`<< /Type /Pages /Kids [${pageObjNums.map(n => `${n} 0 R`).join(' ')}] /Count ${pageCount} >>\nendobj\n`); | |
| for (let i = 0; i < pageCount; i++) { | |
| const { bytes, width, height } = pages[i]; | |
| const pageNum = 3 + i * 3; | |
| const contentNum = pageNum + 1; | |
| const imageNum = pageNum + 2; | |
| startObj(pageNum); | |
| push( | |
| `<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${width} ${height}] ` + | |
| `/Contents ${contentNum} 0 R /Resources << /XObject << /Im0 ${imageNum} 0 R >> >> >>\nendobj\n` | |
| ); | |
| const contentStream = `q ${width} 0 0 ${height} 0 0 cm /Im0 Do Q\n`; | |
| const contentBytes = enc.encode(contentStream); | |
| startObj(contentNum); | |
| push(`<< /Length ${contentBytes.length} >>\nstream\n`); | |
| push(contentBytes); | |
| push('endstream\nendobj\n'); | |
| startObj(imageNum); | |
| push( | |
| `<< /Type /XObject /Subtype /Image /Width ${width} /Height ${height} ` + | |
| `/ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${bytes.length} >>\nstream\n` | |
| ); | |
| push(bytes); | |
| push('\nendstream\nendobj\n'); | |
| } | |
| const objCount = 3 + pageCount * 3; | |
| const xrefOffset = offset; | |
| push(`xref\n0 ${objCount}\n`); | |
| push('0000000000 65535 f \n'); | |
| for (let i = 1; i < objCount; i++) { | |
| push(`${String(xref[i]).padStart(10, '0')} 00000 n \n`); | |
| } | |
| push(`trailer\n<< /Size ${objCount} /Root 1 0 R >>\n`); | |
| push(`startxref\n${xrefOffset}\n%%EOF\n`); | |
| const total = chunks.reduce((n, c) => n + c.length, 0); | |
| const out = new Uint8Array(total); | |
| let pos = 0; | |
| for (const c of chunks) { | |
| out.set(c, pos); | |
| pos += c.length; | |
| } | |
| return out; | |
| } | |
| function downloadBlob(bytes, filename) { | |
| const blob = new Blob([bytes], { type: 'application/pdf' }); | |
| const a = document.createElement('a'); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = filename; | |
| a.click(); | |
| URL.revokeObjectURL(a.href); | |
| } | |
| console.log(`Total pages from fliphtml5_pages: ${maxPage}`); | |
| console.log('Downloading images...'); | |
| const images = []; | |
| for (let i = 1; i <= maxPage; i++) { | |
| console.log(` Downloading page ${i}/${maxPage}...`); | |
| const img = await downloadImage(i); | |
| if (img) { | |
| images.push(img); | |
| } | |
| await wait(delayBetweenDownloads); | |
| } | |
| console.log(`Images downloaded: ${images.length}`); | |
| if (images.length === 0) { | |
| console.error('❌ No images downloaded.'); | |
| return; | |
| } | |
| console.log('Creating PDF (no external library)...'); | |
| const pdfBytes = createPdfFromJpegs(images); | |
| const filename = `magazine_${new Date().toISOString().split('T')[0]}.pdf`; | |
| downloadBlob(pdfBytes, filename); | |
| console.log(`✅ PDF created and saved as ${filename}`); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment