preview.js
1 import { cli, Strategy } from '@jackwener/opencli/registry'; 2 import { 3 getPostDetails, 4 getRawCode, 5 locatePreviewElement, 6 getDefaultOutputPath, 7 saveBase64File, 8 } from './_shared.js'; 9 10 cli({ 11 site: 'uiverse', 12 name: 'preview', 13 description: 'Capture a screenshot of the Uiverse preview element', 14 domain: 'uiverse.io', 15 strategy: Strategy.PUBLIC, 16 browser: true, 17 args: [ 18 { name: 'input', type: 'str', required: true, positional: true, help: 'Uiverse URL or author/slug identifier' }, 19 { name: 'output', type: 'str', required: false, help: 'Output image path (defaults to a temp file)' }, 20 { name: 'padding', type: 'int', required: false, default: 8, help: 'Extra padding around the captured preview in pixels' }, 21 ], 22 columns: ['username', 'slug', 'width', 'height', 'output'], 23 func: async (page, kwargs) => { 24 const detail = await getPostDetails(page, kwargs.input); 25 const payload = await getRawCode(page, detail.post.id); 26 27 const located = await locatePreviewElement(page, payload.html); 28 const rect = located.best.rect; 29 const padding = Math.max(0, Number(kwargs.padding ?? 8)); 30 const clip = { 31 x: Math.max(0, rect.x - padding), 32 y: Math.max(0, rect.y - padding), 33 width: Math.max(1, rect.width + padding * 2), 34 height: Math.max(1, rect.height + padding * 2), 35 scale: 1, 36 }; 37 38 const shot = await page.cdp('Page.captureScreenshot', { 39 format: 'png', 40 clip, 41 captureBeyondViewport: false, 42 }); 43 const base64 = typeof shot === 'string' ? shot : shot?.data; 44 if (!base64) { 45 throw new Error('CDP screenshot failed: no image data was returned.'); 46 } 47 48 const outputPath = kwargs.output || getDefaultOutputPath({ 49 username: detail.username, 50 slug: detail.slug, 51 suffix: 'preview', 52 extension: 'png', 53 }); 54 const savedPath = await saveBase64File(base64, outputPath); 55 56 return { 57 username: detail.username, 58 slug: detail.slug, 59 url: detail.url, 60 output: savedPath, 61 width: Math.round(clip.width), 62 height: Math.round(clip.height), 63 x: Math.round(clip.x), 64 y: Math.round(clip.y), 65 selectorSource: located.best.source, 66 matchedTag: located.best.tag, 67 matchedClassName: located.best.className, 68 postId: detail.post.id, 69 }; 70 }, 71 });