Skip to content

Instantly share code, notes, and snippets.

@marcus-at-localhost
Created July 25, 2026 13:26
Show Gist options
  • Select an option

  • Save marcus-at-localhost/80ea8b7966534966637716bf81792d27 to your computer and use it in GitHub Desktop.

Select an option

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…
// 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