Size
28.4 KB
Version
1.1.1
Created
Dec 21, 2025
Updated
about 1 month ago
1// ==UserScript==
2// @name Genspark Slides Exporter
3// @description Export Genspark slides to PDF without upgrading
4// @version 1.1.1
5// @match https://*.genspark.ai/*
6// @icon https://www.genspark.ai/favicon.ico
7// @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
8// @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
9// @require https://cdnjs.cloudflare.com/ajax/libs/PptxGenJS/3.12.0/pptxgen.min.js
10// ==/UserScript==
11(function() {
12 'use strict';
13
14 console.log('Genspark Slides Exporter loaded');
15
16 // Wait for page to load
17 function waitForElement(selector, timeout = 10000) {
18 return new Promise((resolve, reject) => {
19 if (document.querySelector(selector)) {
20 return resolve(document.querySelector(selector));
21 }
22
23 const observer = new MutationObserver(() => {
24 if (document.querySelector(selector)) {
25 observer.disconnect();
26 resolve(document.querySelector(selector));
27 }
28 });
29
30 observer.observe(document.body, {
31 childList: true,
32 subtree: true
33 });
34
35 setTimeout(() => {
36 observer.disconnect();
37 reject(new Error('Timeout waiting for element: ' + selector));
38 }, timeout);
39 });
40 }
41
42 // Export slides to PDF
43 async function exportSlidesToPDF() {
44 try {
45 console.log('Starting export process...');
46
47 // Show progress indicator
48 const progressDiv = document.createElement('div');
49 progressDiv.id = 'export-progress';
50 progressDiv.style.cssText = `
51 position: fixed;
52 top: 50%;
53 left: 50%;
54 transform: translate(-50%, -50%);
55 background: white;
56 padding: 30px;
57 border-radius: 10px;
58 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
59 z-index: 999999;
60 text-align: center;
61 min-width: 300px;
62 `;
63 progressDiv.innerHTML = `
64 <h3 style="margin: 0 0 15px 0; color: #333;">Exporting Slides</h3>
65 <div style="color: #666; margin-bottom: 10px;">Processing slide <span id="current-slide">0</span> of <span id="total-slides">0</span></div>
66 <div style="background: #f0f0f0; height: 20px; border-radius: 10px; overflow: hidden;">
67 <div id="progress-bar" style="background: linear-gradient(90deg, #4CAF50, #45a049); height: 100%; width: 0%; transition: width 0.3s;"></div>
68 </div>
69 `;
70 document.body.appendChild(progressDiv);
71
72 // Get all slide elements
73 const slideElements = document.querySelectorAll('.slide-content');
74 console.log(`Found ${slideElements.length} slides`);
75
76 if (slideElements.length === 0) {
77 alert('No slides found on this page. Please make sure you are on the slides view.');
78 progressDiv.remove();
79 return;
80 }
81
82 document.getElementById('total-slides').textContent = slideElements.length;
83
84 // Initialize jsPDF
85 const { jsPDF } = window.jspdf;
86 const pdf = new jsPDF({
87 orientation: 'landscape',
88 unit: 'px',
89 format: [1920, 1080]
90 });
91
92 let isFirstPage = true;
93
94 // Process each slide
95 for (let i = 0; i < slideElements.length; i++) {
96 const slide = slideElements[i];
97
98 // Update progress
99 document.getElementById('current-slide').textContent = i + 1;
100 document.getElementById('progress-bar').style.width = ((i + 1) / slideElements.length * 100) + '%';
101
102 console.log(`Processing slide ${i + 1}/${slideElements.length}`);
103
104 // Scroll slide into view
105 slide.scrollIntoView({ behavior: 'instant', block: 'center' });
106
107 // Wait a bit for iframe content to load
108 await new Promise(resolve => setTimeout(resolve, 500));
109
110 try {
111 // Try to capture the iframe content first
112 const iframe = slide.querySelector('iframe');
113 let elementToCapture = slide;
114
115 if (iframe && iframe.contentDocument && iframe.contentDocument.body) {
116 // If we can access iframe content, capture it
117 elementToCapture = iframe.contentDocument.body;
118 console.log('Capturing iframe content for slide', i + 1);
119 } else {
120 // Otherwise capture the whole slide container
121 console.log('Capturing slide container for slide', i + 1);
122 }
123
124 // Capture the slide using html2canvas
125 const canvas = await html2canvas(elementToCapture, {
126 scale: 2,
127 useCORS: true,
128 allowTaint: true,
129 backgroundColor: '#ffffff',
130 logging: false
131 });
132
133 // Add to PDF
134 const imgData = canvas.toDataURL('image/jpeg', 0.95);
135
136 if (!isFirstPage) {
137 pdf.addPage();
138 }
139 isFirstPage = false;
140
141 // Calculate dimensions to fit the page
142 const pdfWidth = pdf.internal.pageSize.getWidth();
143 const pdfHeight = pdf.internal.pageSize.getHeight();
144 const imgWidth = canvas.width;
145 const imgHeight = canvas.height;
146
147 const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
148 const width = imgWidth * ratio;
149 const height = imgHeight * ratio;
150 const x = (pdfWidth - width) / 2;
151 const y = (pdfHeight - height) / 2;
152
153 pdf.addImage(imgData, 'JPEG', x, y, width, height);
154
155 } catch (error) {
156 console.error(`Error capturing slide ${i + 1}:`, error);
157 }
158 }
159
160 // Save the PDF
161 const projectId = new URLSearchParams(window.location.search).get('project_id') || 'slides';
162 const filename = `genspark-slides-${projectId}-${Date.now()}.pdf`;
163 pdf.save(filename);
164
165 console.log('Export completed successfully!');
166 progressDiv.remove();
167
168 // Show success message
169 const successDiv = document.createElement('div');
170 successDiv.style.cssText = `
171 position: fixed;
172 top: 20px;
173 right: 20px;
174 background: #4CAF50;
175 color: white;
176 padding: 15px 25px;
177 border-radius: 5px;
178 box-shadow: 0 2px 10px rgba(0,0,0,0.2);
179 z-index: 999999;
180 font-weight: bold;
181 `;
182 successDiv.textContent = '✓ Slides exported successfully!';
183 document.body.appendChild(successDiv);
184
185 setTimeout(() => successDiv.remove(), 3000);
186
187 } catch (error) {
188 console.error('Export error:', error);
189 alert('Error exporting slides: ' + error.message);
190 const progressDiv = document.getElementById('export-progress');
191 if (progressDiv) progressDiv.remove();
192 }
193 }
194
195 // Export slides to PPTX
196 async function exportSlidesToPPTX() {
197 try {
198 console.log('Starting PPTX export process...');
199
200 // Show progress indicator
201 const progressDiv = document.createElement('div');
202 progressDiv.id = 'export-progress';
203 progressDiv.style.cssText = `
204 position: fixed;
205 top: 50%;
206 left: 50%;
207 transform: translate(-50%, -50%);
208 background: white;
209 padding: 30px;
210 border-radius: 10px;
211 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
212 z-index: 999999;
213 text-align: center;
214 min-width: 300px;
215 `;
216 progressDiv.innerHTML = `
217 <h3 style="margin: 0 0 15px 0; color: #333;">Exporting to PowerPoint</h3>
218 <div style="color: #666; margin-bottom: 10px;">Processing slide <span id="current-slide">0</span> of <span id="total-slides">0</span></div>
219 <div style="background: #f0f0f0; height: 20px; border-radius: 10px; overflow: hidden;">
220 <div id="progress-bar" style="background: linear-gradient(90deg, #FF6B35, #F7931E); height: 100%; width: 0%; transition: width 0.3s;"></div>
221 </div>
222 `;
223 document.body.appendChild(progressDiv);
224
225 // Get all slide elements
226 const slideElements = document.querySelectorAll('.slide-content');
227 console.log(`Found ${slideElements.length} slides`);
228
229 if (slideElements.length === 0) {
230 alert('No slides found on this page. Please make sure you are on the slides view.');
231 progressDiv.remove();
232 return;
233 }
234
235 document.getElementById('total-slides').textContent = slideElements.length;
236
237 // Initialize PptxGenJS
238 const pptx = new PptxGenJS();
239 pptx.layout = 'LAYOUT_16x9';
240
241 // Process each slide
242 for (let i = 0; i < slideElements.length; i++) {
243 const slide = slideElements[i];
244
245 // Update progress
246 document.getElementById('current-slide').textContent = i + 1;
247 document.getElementById('progress-bar').style.width = ((i + 1) / slideElements.length * 100) + '%';
248
249 console.log(`Processing slide ${i + 1}/${slideElements.length}`);
250
251 // Scroll slide into view
252 slide.scrollIntoView({ behavior: 'instant', block: 'center' });
253
254 // Wait a bit for iframe content to load
255 await new Promise(resolve => setTimeout(resolve, 500));
256
257 try {
258 // Try to capture the iframe content first
259 const iframe = slide.querySelector('iframe');
260 let elementToCapture = slide;
261
262 if (iframe && iframe.contentDocument && iframe.contentDocument.body) {
263 elementToCapture = iframe.contentDocument.body;
264 console.log('Capturing iframe content for slide', i + 1);
265 } else {
266 console.log('Capturing slide container for slide', i + 1);
267 }
268
269 // Capture the slide using html2canvas
270 const canvas = await html2canvas(elementToCapture, {
271 scale: 2,
272 useCORS: true,
273 allowTaint: true,
274 backgroundColor: '#ffffff',
275 logging: false
276 });
277
278 // Convert canvas to base64 image
279 const imgData = canvas.toDataURL('image/png');
280
281 // Add slide to presentation
282 const pptxSlide = pptx.addSlide();
283 pptxSlide.addImage({
284 data: imgData,
285 x: 0,
286 y: 0,
287 w: '100%',
288 h: '100%'
289 });
290
291 } catch (error) {
292 console.error(`Error capturing slide ${i + 1}:`, error);
293 }
294 }
295
296 // Save the PPTX
297 const projectId = new URLSearchParams(window.location.search).get('project_id') || 'slides';
298 const filename = `genspark-slides-${projectId}-${Date.now()}`;
299 await pptx.writeFile({ fileName: filename });
300
301 console.log('PPTX export completed successfully!');
302 progressDiv.remove();
303
304 // Show success message
305 const successDiv = document.createElement('div');
306 successDiv.style.cssText = `
307 position: fixed;
308 top: 20px;
309 right: 20px;
310 background: #FF6B35;
311 color: white;
312 padding: 15px 25px;
313 border-radius: 5px;
314 box-shadow: 0 2px 10px rgba(0,0,0,0.2);
315 z-index: 999999;
316 font-weight: bold;
317 `;
318 successDiv.textContent = '✓ PowerPoint exported successfully!';
319 document.body.appendChild(successDiv);
320
321 setTimeout(() => successDiv.remove(), 3000);
322
323 } catch (error) {
324 console.error('PPTX export error:', error);
325 alert('Error exporting to PowerPoint: ' + error.message);
326 const progressDiv = document.getElementById('export-progress');
327 if (progressDiv) progressDiv.remove();
328 }
329 }
330
331 // Export slides to Google Slides
332 async function exportToGoogleSlides() {
333 try {
334 console.log('Starting Google Slides export process...');
335
336 // Show progress indicator
337 const progressDiv = document.createElement('div');
338 progressDiv.id = 'export-progress';
339 progressDiv.style.cssText = `
340 position: fixed;
341 top: 50%;
342 left: 50%;
343 transform: translate(-50%, -50%);
344 background: white;
345 padding: 30px;
346 border-radius: 10px;
347 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
348 z-index: 999999;
349 text-align: center;
350 min-width: 300px;
351 `;
352 progressDiv.innerHTML = `
353 <h3 style="margin: 0 0 15px 0; color: #333;">Exporting to Google Slides</h3>
354 <div style="color: #666; margin-bottom: 15px;">First, we'll create a PowerPoint file, then you can upload it to Google Slides</div>
355 <div style="color: #666; margin-bottom: 10px;">Processing slide <span id="current-slide">0</span> of <span id="total-slides">0</span></div>
356 <div style="background: #f0f0f0; height: 20px; border-radius: 10px; overflow: hidden;">
357 <div id="progress-bar" style="background: linear-gradient(90deg, #4285F4, #34A853); height: 100%; width: 0%; transition: width 0.3s;"></div>
358 </div>
359 `;
360 document.body.appendChild(progressDiv);
361
362 // Get all slide elements
363 const slideElements = document.querySelectorAll('.slide-content');
364 console.log(`Found ${slideElements.length} slides`);
365
366 if (slideElements.length === 0) {
367 alert('No slides found on this page. Please make sure you are on the slides view.');
368 progressDiv.remove();
369 return;
370 }
371
372 document.getElementById('total-slides').textContent = slideElements.length;
373
374 // Initialize PptxGenJS (we'll create PPTX that can be uploaded to Google Slides)
375 const pptx = new PptxGenJS();
376 pptx.layout = 'LAYOUT_16x9';
377
378 // Process each slide
379 for (let i = 0; i < slideElements.length; i++) {
380 const slide = slideElements[i];
381
382 // Update progress
383 document.getElementById('current-slide').textContent = i + 1;
384 document.getElementById('progress-bar').style.width = ((i + 1) / slideElements.length * 100) + '%';
385
386 console.log(`Processing slide ${i + 1}/${slideElements.length}`);
387
388 // Scroll slide into view
389 slide.scrollIntoView({ behavior: 'instant', block: 'center' });
390
391 // Wait a bit for iframe content to load
392 await new Promise(resolve => setTimeout(resolve, 500));
393
394 try {
395 // Try to capture the iframe content first
396 const iframe = slide.querySelector('iframe');
397 let elementToCapture = slide;
398
399 if (iframe && iframe.contentDocument && iframe.contentDocument.body) {
400 elementToCapture = iframe.contentDocument.body;
401 console.log('Capturing iframe content for slide', i + 1);
402 } else {
403 console.log('Capturing slide container for slide', i + 1);
404 }
405
406 // Capture the slide using html2canvas
407 const canvas = await html2canvas(elementToCapture, {
408 scale: 2,
409 useCORS: true,
410 allowTaint: true,
411 backgroundColor: '#ffffff',
412 logging: false
413 });
414
415 // Convert canvas to base64 image
416 const imgData = canvas.toDataURL('image/png');
417
418 // Add slide to presentation
419 const pptxSlide = pptx.addSlide();
420 pptxSlide.addImage({
421 data: imgData,
422 x: 0,
423 y: 0,
424 w: '100%',
425 h: '100%'
426 });
427
428 } catch (error) {
429 console.error(`Error capturing slide ${i + 1}:`, error);
430 }
431 }
432
433 // Save the PPTX
434 const projectId = new URLSearchParams(window.location.search).get('project_id') || 'slides';
435 const filename = `genspark-slides-${projectId}-${Date.now()}`;
436 await pptx.writeFile({ fileName: filename });
437
438 console.log('Google Slides export completed successfully!');
439 progressDiv.remove();
440
441 // Show success message with instructions
442 const successDiv = document.createElement('div');
443 successDiv.style.cssText = `
444 position: fixed;
445 top: 50%;
446 left: 50%;
447 transform: translate(-50%, -50%);
448 background: white;
449 padding: 30px;
450 border-radius: 10px;
451 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
452 z-index: 999999;
453 max-width: 500px;
454 `;
455 successDiv.innerHTML = `
456 <h3 style="margin: 0 0 15px 0; color: #333;">✓ PowerPoint file downloaded!</h3>
457 <p style="color: #666; margin-bottom: 15px;">To open in Google Slides:</p>
458 <ol style="color: #666; text-align: left; margin-bottom: 20px;">
459 <li>Go to <a href="https://slides.google.com" target="_blank" style="color: #4285F4;">slides.google.com</a></li>
460 <li>Click "File" → "Open" → "Upload"</li>
461 <li>Select the downloaded PowerPoint file</li>
462 </ol>
463 <button id="open-google-slides-btn" style="background: #4285F4; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-weight: bold; margin-right: 10px;">Open Google Slides</button>
464 <button id="close-success-btn" style="background: #666; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer;">Close</button>
465 `;
466 document.body.appendChild(successDiv);
467
468 document.getElementById('open-google-slides-btn').onclick = () => {
469 window.open('https://slides.google.com', '_blank');
470 successDiv.remove();
471 };
472
473 document.getElementById('close-success-btn').onclick = () => {
474 successDiv.remove();
475 };
476
477 } catch (error) {
478 console.error('Google Slides export error:', error);
479 alert('Error exporting to Google Slides: ' + error.message);
480 const progressDiv = document.getElementById('export-progress');
481 if (progressDiv) progressDiv.remove();
482 }
483 }
484
485 // Add export button to the page
486 async function addExportButton() {
487 try {
488 // Wait for the slides container to load
489 await waitForElement('.keynote-main');
490
491 console.log('Adding export button...');
492
493 // Find the export button area
494 const exportButtonArea = document.querySelector('.button-play-slides');
495
496 if (!exportButtonArea) {
497 console.log('Export button area not found, creating custom buttons');
498 // Create floating buttons if we can't find the export area
499 const buttonContainer = document.createElement('div');
500 buttonContainer.style.cssText = `
501 position: fixed;
502 bottom: 30px;
503 right: 30px;
504 display: flex;
505 flex-direction: column;
506 gap: 10px;
507 z-index: 999998;
508 `;
509
510 // PDF Button
511 const pdfBtn = document.createElement('button');
512 pdfBtn.textContent = '📄 Export to PDF';
513 pdfBtn.style.cssText = `
514 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
515 color: white;
516 border: none;
517 padding: 15px 25px;
518 border-radius: 8px;
519 font-size: 16px;
520 font-weight: bold;
521 cursor: pointer;
522 box-shadow: 0 4px 15px rgba(0,0,0,0.2);
523 transition: transform 0.2s, box-shadow 0.2s;
524 `;
525 pdfBtn.onmouseover = () => {
526 pdfBtn.style.transform = 'translateY(-2px)';
527 pdfBtn.style.boxShadow = '0 6px 20px rgba(0,0,0,0.3)';
528 };
529 pdfBtn.onmouseout = () => {
530 pdfBtn.style.transform = 'translateY(0)';
531 pdfBtn.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
532 };
533 pdfBtn.onclick = exportSlidesToPDF;
534
535 // PPTX Button
536 const pptxBtn = document.createElement('button');
537 pptxBtn.textContent = '📊 Export to PowerPoint';
538 pptxBtn.style.cssText = `
539 background: linear-gradient(135deg, #FF6B35 0%, #F7931E 100%);
540 color: white;
541 border: none;
542 padding: 15px 25px;
543 border-radius: 8px;
544 font-size: 16px;
545 font-weight: bold;
546 cursor: pointer;
547 box-shadow: 0 4px 15px rgba(0,0,0,0.2);
548 transition: transform 0.2s, box-shadow 0.2s;
549 `;
550 pptxBtn.onmouseover = () => {
551 pptxBtn.style.transform = 'translateY(-2px)';
552 pptxBtn.style.boxShadow = '0 6px 20px rgba(0,0,0,0.3)';
553 };
554 pptxBtn.onmouseout = () => {
555 pptxBtn.style.transform = 'translateY(0)';
556 pptxBtn.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
557 };
558 pptxBtn.onclick = exportSlidesToPPTX;
559
560 // Google Slides Button
561 const gSlidesBtn = document.createElement('button');
562 gSlidesBtn.textContent = '🎨 Export to Google Slides';
563 gSlidesBtn.style.cssText = `
564 background: linear-gradient(135deg, #4285F4 0%, #34A853 100%);
565 color: white;
566 border: none;
567 padding: 15px 25px;
568 border-radius: 8px;
569 font-size: 16px;
570 font-weight: bold;
571 cursor: pointer;
572 box-shadow: 0 4px 15px rgba(0,0,0,0.2);
573 transition: transform 0.2s, box-shadow 0.2s;
574 `;
575 gSlidesBtn.onmouseover = () => {
576 gSlidesBtn.style.transform = 'translateY(-2px)';
577 gSlidesBtn.style.boxShadow = '0 6px 20px rgba(0,0,0,0.3)';
578 };
579 gSlidesBtn.onmouseout = () => {
580 gSlidesBtn.style.transform = 'translateY(0)';
581 gSlidesBtn.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
582 };
583 gSlidesBtn.onclick = exportToGoogleSlides;
584
585 buttonContainer.appendChild(pdfBtn);
586 buttonContainer.appendChild(pptxBtn);
587 buttonContainer.appendChild(gSlidesBtn);
588 document.body.appendChild(buttonContainer);
589 console.log('Floating export buttons added');
590 return;
591 }
592
593 // Clone the existing export button and modify it for PDF
594 const pdfExportBtn = exportButtonArea.cloneNode(true);
595 pdfExportBtn.querySelector('.play-slides-text').textContent = 'PDF';
596 const plusBadge1 = pdfExportBtn.querySelector('.plus-badge');
597 if (plusBadge1) plusBadge1.remove();
598 pdfExportBtn.style.cssText = `
599 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
600 cursor: pointer;
601 margin-left: 10px;
602 `;
603 pdfExportBtn.onclick = (e) => {
604 e.preventDefault();
605 e.stopPropagation();
606 exportSlidesToPDF();
607 };
608
609 // Clone for PPTX
610 const pptxExportBtn = exportButtonArea.cloneNode(true);
611 pptxExportBtn.querySelector('.play-slides-text').textContent = 'PPTX';
612 const plusBadge2 = pptxExportBtn.querySelector('.plus-badge');
613 if (plusBadge2) plusBadge2.remove();
614 pptxExportBtn.style.cssText = `
615 background: linear-gradient(135deg, #FF6B35 0%, #F7931E 100%);
616 cursor: pointer;
617 margin-left: 10px;
618 `;
619 pptxExportBtn.onclick = (e) => {
620 e.preventDefault();
621 e.stopPropagation();
622 exportSlidesToPPTX();
623 };
624
625 // Clone for Google Slides
626 const gSlidesExportBtn = exportButtonArea.cloneNode(true);
627 gSlidesExportBtn.querySelector('.play-slides-text').textContent = 'Google Slides';
628 const plusBadge3 = gSlidesExportBtn.querySelector('.plus-badge');
629 if (plusBadge3) plusBadge3.remove();
630 gSlidesExportBtn.style.cssText = `
631 background: linear-gradient(135deg, #4285F4 0%, #34A853 100%);
632 cursor: pointer;
633 margin-left: 10px;
634 `;
635 gSlidesExportBtn.onclick = (e) => {
636 e.preventDefault();
637 e.stopPropagation();
638 exportToGoogleSlides();
639 };
640
641 // Insert the buttons next to the original export button
642 exportButtonArea.parentElement.insertBefore(pdfExportBtn, exportButtonArea.nextSibling);
643 exportButtonArea.parentElement.insertBefore(pptxExportBtn, pdfExportBtn.nextSibling);
644 exportButtonArea.parentElement.insertBefore(gSlidesExportBtn, pptxExportBtn.nextSibling);
645
646 console.log('Export buttons added successfully');
647
648 } catch (error) {
649 console.error('Error adding export button:', error);
650 }
651 }
652
653 // Initialize
654 function init() {
655 console.log('Initializing Genspark Slides Exporter...');
656
657 // Check if we're on the slides page
658 if (window.location.pathname.includes('/slides')) {
659 addExportButton();
660 }
661 }
662
663 // Run when page loads
664 if (document.readyState === 'loading') {
665 document.addEventListener('DOMContentLoaded', init);
666 } else {
667 init();
668 }
669
670})();