Size
11.3 KB
Version
1.0.0
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.0.0
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// ==/UserScript==
10(function() {
11 'use strict';
12
13 console.log('Genspark Slides Exporter loaded');
14
15 // Wait for page to load
16 function waitForElement(selector, timeout = 10000) {
17 return new Promise((resolve, reject) => {
18 if (document.querySelector(selector)) {
19 return resolve(document.querySelector(selector));
20 }
21
22 const observer = new MutationObserver(() => {
23 if (document.querySelector(selector)) {
24 observer.disconnect();
25 resolve(document.querySelector(selector));
26 }
27 });
28
29 observer.observe(document.body, {
30 childList: true,
31 subtree: true
32 });
33
34 setTimeout(() => {
35 observer.disconnect();
36 reject(new Error('Timeout waiting for element: ' + selector));
37 }, timeout);
38 });
39 }
40
41 // Export slides to PDF
42 async function exportSlidesToPDF() {
43 try {
44 console.log('Starting export process...');
45
46 // Show progress indicator
47 const progressDiv = document.createElement('div');
48 progressDiv.id = 'export-progress';
49 progressDiv.style.cssText = `
50 position: fixed;
51 top: 50%;
52 left: 50%;
53 transform: translate(-50%, -50%);
54 background: white;
55 padding: 30px;
56 border-radius: 10px;
57 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
58 z-index: 999999;
59 text-align: center;
60 min-width: 300px;
61 `;
62 progressDiv.innerHTML = `
63 <h3 style="margin: 0 0 15px 0; color: #333;">Exporting Slides</h3>
64 <div style="color: #666; margin-bottom: 10px;">Processing slide <span id="current-slide">0</span> of <span id="total-slides">0</span></div>
65 <div style="background: #f0f0f0; height: 20px; border-radius: 10px; overflow: hidden;">
66 <div id="progress-bar" style="background: linear-gradient(90deg, #4CAF50, #45a049); height: 100%; width: 0%; transition: width 0.3s;"></div>
67 </div>
68 `;
69 document.body.appendChild(progressDiv);
70
71 // Get all slide elements
72 const slideElements = document.querySelectorAll('.slide-content');
73 console.log(`Found ${slideElements.length} slides`);
74
75 if (slideElements.length === 0) {
76 alert('No slides found on this page. Please make sure you are on the slides view.');
77 progressDiv.remove();
78 return;
79 }
80
81 document.getElementById('total-slides').textContent = slideElements.length;
82
83 // Initialize jsPDF
84 const { jsPDF } = window.jspdf;
85 const pdf = new jsPDF({
86 orientation: 'landscape',
87 unit: 'px',
88 format: [1920, 1080]
89 });
90
91 let isFirstPage = true;
92
93 // Process each slide
94 for (let i = 0; i < slideElements.length; i++) {
95 const slide = slideElements[i];
96
97 // Update progress
98 document.getElementById('current-slide').textContent = i + 1;
99 document.getElementById('progress-bar').style.width = ((i + 1) / slideElements.length * 100) + '%';
100
101 console.log(`Processing slide ${i + 1}/${slideElements.length}`);
102
103 // Scroll slide into view
104 slide.scrollIntoView({ behavior: 'instant', block: 'center' });
105
106 // Wait a bit for iframe content to load
107 await new Promise(resolve => setTimeout(resolve, 500));
108
109 try {
110 // Try to capture the iframe content first
111 const iframe = slide.querySelector('iframe');
112 let elementToCapture = slide;
113
114 if (iframe && iframe.contentDocument && iframe.contentDocument.body) {
115 // If we can access iframe content, capture it
116 elementToCapture = iframe.contentDocument.body;
117 console.log('Capturing iframe content for slide', i + 1);
118 } else {
119 // Otherwise capture the whole slide container
120 console.log('Capturing slide container for slide', i + 1);
121 }
122
123 // Capture the slide using html2canvas
124 const canvas = await html2canvas(elementToCapture, {
125 scale: 2,
126 useCORS: true,
127 allowTaint: true,
128 backgroundColor: '#ffffff',
129 logging: false
130 });
131
132 // Add to PDF
133 const imgData = canvas.toDataURL('image/jpeg', 0.95);
134
135 if (!isFirstPage) {
136 pdf.addPage();
137 }
138 isFirstPage = false;
139
140 // Calculate dimensions to fit the page
141 const pdfWidth = pdf.internal.pageSize.getWidth();
142 const pdfHeight = pdf.internal.pageSize.getHeight();
143 const imgWidth = canvas.width;
144 const imgHeight = canvas.height;
145
146 const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
147 const width = imgWidth * ratio;
148 const height = imgHeight * ratio;
149 const x = (pdfWidth - width) / 2;
150 const y = (pdfHeight - height) / 2;
151
152 pdf.addImage(imgData, 'JPEG', x, y, width, height);
153
154 } catch (error) {
155 console.error(`Error capturing slide ${i + 1}:`, error);
156 }
157 }
158
159 // Save the PDF
160 const projectId = new URLSearchParams(window.location.search).get('project_id') || 'slides';
161 const filename = `genspark-slides-${projectId}-${Date.now()}.pdf`;
162 pdf.save(filename);
163
164 console.log('Export completed successfully!');
165 progressDiv.remove();
166
167 // Show success message
168 const successDiv = document.createElement('div');
169 successDiv.style.cssText = `
170 position: fixed;
171 top: 20px;
172 right: 20px;
173 background: #4CAF50;
174 color: white;
175 padding: 15px 25px;
176 border-radius: 5px;
177 box-shadow: 0 2px 10px rgba(0,0,0,0.2);
178 z-index: 999999;
179 font-weight: bold;
180 `;
181 successDiv.textContent = '✓ Slides exported successfully!';
182 document.body.appendChild(successDiv);
183
184 setTimeout(() => successDiv.remove(), 3000);
185
186 } catch (error) {
187 console.error('Export error:', error);
188 alert('Error exporting slides: ' + error.message);
189 const progressDiv = document.getElementById('export-progress');
190 if (progressDiv) progressDiv.remove();
191 }
192 }
193
194 // Add export button to the page
195 async function addExportButton() {
196 try {
197 // Wait for the slides container to load
198 await waitForElement('.keynote-main');
199
200 console.log('Adding export button...');
201
202 // Find the export button area
203 const exportButtonArea = document.querySelector('.button-play-slides');
204
205 if (!exportButtonArea) {
206 console.log('Export button area not found, creating custom button');
207 // Create a floating button if we can't find the export area
208 const floatingBtn = document.createElement('button');
209 floatingBtn.textContent = '📥 Export to PDF (Free)';
210 floatingBtn.style.cssText = `
211 position: fixed;
212 bottom: 30px;
213 right: 30px;
214 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
215 color: white;
216 border: none;
217 padding: 15px 25px;
218 border-radius: 8px;
219 font-size: 16px;
220 font-weight: bold;
221 cursor: pointer;
222 box-shadow: 0 4px 15px rgba(0,0,0,0.2);
223 z-index: 999998;
224 transition: transform 0.2s, box-shadow 0.2s;
225 `;
226 floatingBtn.onmouseover = () => {
227 floatingBtn.style.transform = 'translateY(-2px)';
228 floatingBtn.style.boxShadow = '0 6px 20px rgba(0,0,0,0.3)';
229 };
230 floatingBtn.onmouseout = () => {
231 floatingBtn.style.transform = 'translateY(0)';
232 floatingBtn.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
233 };
234 floatingBtn.onclick = exportSlidesToPDF;
235 document.body.appendChild(floatingBtn);
236 console.log('Floating export button added');
237 return;
238 }
239
240 // Clone the existing export button and modify it
241 const freeExportBtn = exportButtonArea.cloneNode(true);
242 freeExportBtn.querySelector('.play-slides-text').textContent = 'Export (Free)';
243
244 // Remove the Plus badge
245 const plusBadge = freeExportBtn.querySelector('.plus-badge');
246 if (plusBadge) plusBadge.remove();
247
248 // Style the button
249 freeExportBtn.style.cssText = `
250 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
251 cursor: pointer;
252 margin-left: 10px;
253 `;
254
255 // Add click handler
256 freeExportBtn.onclick = (e) => {
257 e.preventDefault();
258 e.stopPropagation();
259 exportSlidesToPDF();
260 };
261
262 // Insert the button next to the original export button
263 exportButtonArea.parentElement.insertBefore(freeExportBtn, exportButtonArea.nextSibling);
264
265 console.log('Export button added successfully');
266
267 } catch (error) {
268 console.error('Error adding export button:', error);
269 }
270 }
271
272 // Initialize
273 function init() {
274 console.log('Initializing Genspark Slides Exporter...');
275
276 // Check if we're on the slides page
277 if (window.location.pathname.includes('/slides')) {
278 addExportButton();
279 }
280 }
281
282 // Run when page loads
283 if (document.readyState === 'loading') {
284 document.addEventListener('DOMContentLoaded', init);
285 } else {
286 init();
287 }
288
289})();