SEO Meta & Structured Data Explorer

Extract OpenGraph, Twitter Cards, and Google structured data that affects search results appearance

Size

14.4 KB

Version

1.0.1

Created

Apr 5, 2026

Updated

11 days ago

1// ==UserScript==
2// @name		SEO Meta & Structured Data Explorer
3// @description		Extract OpenGraph, Twitter Cards, and Google structured data that affects search results appearance
4// @version		1.0.1
5// @match		*://*/*
6// @icon		https://framerusercontent.com/images/30hVevKHnJCJrY87n1ScK6rwCM4.png
7// ==/UserScript==
8(function() {
9    'use strict';
10    
11    console.log('OpenGraph Explorer: Extension loaded');
12    
13    // Debounce function to prevent excessive calls
14    function debounce(func, wait) {
15        let timeout;
16        return function executedFunction(...args) {
17            const later = () => {
18                clearTimeout(timeout);
19                func(...args);
20            };
21            clearTimeout(timeout);
22            timeout = setTimeout(later, wait);
23        };
24    }
25    
26    // Extract OpenGraph meta tags from the page
27    function extractOpenGraphData() {
28        console.log('OpenGraph Explorer: Extracting OpenGraph data');
29        const ogTags = {};
30        const metaTags = document.querySelectorAll('meta[property^="og:"], meta[name^="og:"]');
31        
32        metaTags.forEach(tag => {
33            const property = tag.getAttribute('property') || tag.getAttribute('name');
34            const content = tag.getAttribute('content');
35            if (property && content) {
36                ogTags[property] = content;
37            }
38        });
39        
40        // Also check for Twitter Card tags as they're related
41        const twitterTags = document.querySelectorAll('meta[name^="twitter:"]');
42        twitterTags.forEach(tag => {
43            const name = tag.getAttribute('name');
44            const content = tag.getAttribute('content');
45            if (name && content) {
46                ogTags[name] = content;
47            }
48        });
49        
50        console.log('OpenGraph Explorer: Found tags:', ogTags);
51        return ogTags;
52    }
53    
54    // Create the OpenGraph Explorer UI
55    function createOpenGraphExplorer() {
56        console.log('OpenGraph Explorer: Creating UI');
57        
58        // Remove existing explorer if it exists
59        const existingExplorer = document.getElementById('og-explorer');
60        if (existingExplorer) {
61            existingExplorer.remove();
62        }
63        
64        const existingToggle = document.getElementById('og-toggle-btn');
65        if (existingToggle) {
66            existingToggle.remove();
67        }
68        
69        // Create toggle button
70        const toggleBtn = document.createElement('button');
71        toggleBtn.id = 'og-toggle-btn';
72        toggleBtn.innerHTML = '🔍 OG';
73        toggleBtn.title = 'Toggle OpenGraph Explorer';
74        toggleBtn.style.cssText = `
75            position: fixed;
76            top: 20px;
77            right: 20px;
78            z-index: 10000;
79            background: #2563eb;
80            color: white;
81            border: none;
82            border-radius: 8px;
83            padding: 10px 12px;
84            font-size: 14px;
85            font-weight: 600;
86            cursor: pointer;
87            box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
88            transition: all 0.2s ease;
89        `;
90        
91        // Create main explorer panel
92        const explorer = document.createElement('div');
93        explorer.id = 'og-explorer';
94        explorer.style.cssText = `
95            position: fixed;
96            top: 70px;
97            right: 20px;
98            width: 400px;
99            max-height: 80vh;
100            background: white;
101            border: 1px solid #e5e7eb;
102            border-radius: 12px;
103            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
104            z-index: 9999;
105            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
106            display: none;
107            overflow: hidden;
108        `;
109        
110        // Extract OpenGraph data
111        const ogData = extractOpenGraphData();
112        const hasData = Object.keys(ogData).length > 0;
113        
114        // Create header
115        const header = document.createElement('div');
116        header.style.cssText = `
117            background: #f8fafc;
118            padding: 16px 20px;
119            border-bottom: 1px solid #e5e7eb;
120            display: flex;
121            justify-content: space-between;
122            align-items: center;
123        `;
124        
125        const title = document.createElement('h3');
126        title.textContent = 'OpenGraph Explorer';
127        title.style.cssText = `
128            margin: 0;
129            font-size: 16px;
130            font-weight: 600;
131            color: #1f2937;
132        `;
133        
134        const closeBtn = document.createElement('button');
135        closeBtn.innerHTML = '×';
136        closeBtn.style.cssText = `
137            background: none;
138            border: none;
139            font-size: 20px;
140            color: #6b7280;
141            cursor: pointer;
142            padding: 0;
143            width: 24px;
144            height: 24px;
145            display: flex;
146            align-items: center;
147            justify-content: center;
148        `;
149        
150        header.appendChild(title);
151        header.appendChild(closeBtn);
152        
153        // Create content area
154        const content = document.createElement('div');
155        content.style.cssText = `
156            max-height: 60vh;
157            overflow-y: auto;
158            padding: 0;
159        `;
160        
161        if (!hasData) {
162            const noDataMsg = document.createElement('div');
163            noDataMsg.style.cssText = `
164                padding: 40px 20px;
165                text-align: center;
166                color: #6b7280;
167            `;
168            noDataMsg.innerHTML = `
169                <div style="font-size: 48px; margin-bottom: 16px;">📄</div>
170                <div style="font-size: 16px; font-weight: 500; margin-bottom: 8px;">No OpenGraph Tags Found</div>
171                <div style="font-size: 14px;">This page doesn't have OpenGraph meta tags.</div>
172            `;
173            content.appendChild(noDataMsg);
174        } else {
175            // Create copy all button
176            const copyAllBtn = document.createElement('button');
177            copyAllBtn.textContent = 'Copy All Data';
178            copyAllBtn.style.cssText = `
179                width: calc(100% - 40px);
180                margin: 20px;
181                padding: 10px;
182                background: #10b981;
183                color: white;
184                border: none;
185                border-radius: 6px;
186                font-size: 14px;
187                font-weight: 500;
188                cursor: pointer;
189                transition: background 0.2s ease;
190            `;
191            
192            copyAllBtn.addEventListener('click', async () => {
193                try {
194                    const jsonData = JSON.stringify(ogData, null, 2);
195                    await GM.setClipboard(jsonData);
196                    copyAllBtn.textContent = 'Copied!';
197                    copyAllBtn.style.background = '#059669';
198                    setTimeout(() => {
199                        copyAllBtn.textContent = 'Copy All Data';
200                        copyAllBtn.style.background = '#10b981';
201                    }, 2000);
202                } catch (error) {
203                    console.error('OpenGraph Explorer: Copy failed:', error);
204                }
205            });
206            
207            content.appendChild(copyAllBtn);
208            
209            // Create tags list
210            const tagsList = document.createElement('div');
211            tagsList.style.cssText = `
212                padding: 0 20px 20px;
213            `;
214            
215            Object.entries(ogData).forEach(([property, value]) => {
216                const tagItem = document.createElement('div');
217                tagItem.style.cssText = `
218                    margin-bottom: 16px;
219                    padding: 12px;
220                    background: #f9fafb;
221                    border: 1px solid #e5e7eb;
222                    border-radius: 8px;
223                `;
224                
225                const propertyLabel = document.createElement('div');
226                propertyLabel.textContent = property;
227                propertyLabel.style.cssText = `
228                    font-size: 12px;
229                    font-weight: 600;
230                    color: #374151;
231                    margin-bottom: 6px;
232                    text-transform: uppercase;
233                    letter-spacing: 0.5px;
234                `;
235                
236                const valueContainer = document.createElement('div');
237                valueContainer.style.cssText = `
238                    display: flex;
239                    align-items: flex-start;
240                    gap: 8px;
241                `;
242                
243                const valueText = document.createElement('div');
244                valueText.style.cssText = `
245                    flex: 1;
246                    font-size: 14px;
247                    color: #1f2937;
248                    word-break: break-word;
249                    line-height: 1.4;
250                `;
251                
252                // Handle different types of content
253                if (property.includes('image') && value.startsWith('http')) {
254                    const img = document.createElement('img');
255                    img.src = value;
256                    img.style.cssText = `
257                        max-width: 100%;
258                        max-height: 100px;
259                        border-radius: 4px;
260                        margin-bottom: 8px;
261                        display: block;
262                    `;
263                    valueText.appendChild(img);
264                    
265                    const urlText = document.createElement('div');
266                    urlText.textContent = value;
267                    urlText.style.cssText = `
268                        font-size: 12px;
269                        color: #6b7280;
270                        word-break: break-all;
271                    `;
272                    valueText.appendChild(urlText);
273                } else if (value.startsWith('http')) {
274                    const link = document.createElement('a');
275                    link.href = value;
276                    link.textContent = value;
277                    link.target = '_blank';
278                    link.style.cssText = `
279                        color: #2563eb;
280                        text-decoration: none;
281                    `;
282                    valueText.appendChild(link);
283                } else {
284                    valueText.textContent = value;
285                }
286                
287                const copyBtn = document.createElement('button');
288                copyBtn.innerHTML = '📋';
289                copyBtn.title = 'Copy value';
290                copyBtn.style.cssText = `
291                    background: none;
292                    border: 1px solid #d1d5db;
293                    border-radius: 4px;
294                    padding: 4px 6px;
295                    cursor: pointer;
296                    font-size: 12px;
297                    color: #6b7280;
298                    transition: all 0.2s ease;
299                `;
300                
301                copyBtn.addEventListener('click', async () => {
302                    try {
303                        await GM.setClipboard(value);
304                        copyBtn.innerHTML = '✓';
305                        copyBtn.style.color = '#10b981';
306                        setTimeout(() => {
307                            copyBtn.innerHTML = '📋';
308                            copyBtn.style.color = '#6b7280';
309                        }, 2000);
310                    } catch (error) {
311                        console.error('OpenGraph Explorer: Copy failed:', error);
312                    }
313                });
314                
315                valueContainer.appendChild(valueText);
316                valueContainer.appendChild(copyBtn);
317                
318                tagItem.appendChild(propertyLabel);
319                tagItem.appendChild(valueContainer);
320                tagsList.appendChild(tagItem);
321            });
322            
323            content.appendChild(tagsList);
324        }
325        
326        explorer.appendChild(header);
327        explorer.appendChild(content);
328        
329        // Add event listeners
330        let isVisible = false;
331        
332        toggleBtn.addEventListener('click', () => {
333            isVisible = !isVisible;
334            explorer.style.display = isVisible ? 'block' : 'none';
335            toggleBtn.style.background = isVisible ? '#1d4ed8' : '#2563eb';
336        });
337        
338        closeBtn.addEventListener('click', () => {
339            isVisible = false;
340            explorer.style.display = 'none';
341            toggleBtn.style.background = '#2563eb';
342        });
343        
344        // Add hover effects
345        toggleBtn.addEventListener('mouseenter', () => {
346            if (!isVisible) {
347                toggleBtn.style.background = '#1d4ed8';
348            }
349        });
350        
351        toggleBtn.addEventListener('mouseleave', () => {
352            if (!isVisible) {
353                toggleBtn.style.background = '#2563eb';
354            }
355        });
356        
357        // Append to page
358        document.body.appendChild(toggleBtn);
359        document.body.appendChild(explorer);
360        
361        console.log('OpenGraph Explorer: UI created successfully');
362    }
363    
364    // Initialize the extension
365    function init() {
366        console.log('OpenGraph Explorer: Initializing extension');
367        
368        // Wait for page to be ready
369        if (document.readyState === 'loading') {
370            document.addEventListener('DOMContentLoaded', createOpenGraphExplorer);
371        } else {
372            createOpenGraphExplorer();
373        }
374        
375        // Re-scan for OpenGraph tags when page content changes
376        const debouncedUpdate = debounce(createOpenGraphExplorer, 1000);
377        
378        const observer = new MutationObserver((mutations) => {
379            let shouldUpdate = false;
380            mutations.forEach((mutation) => {
381                if (mutation.type === 'childList') {
382                    mutation.addedNodes.forEach((node) => {
383                        if (node.nodeType === Node.ELEMENT_NODE) {
384                            if (node.tagName === 'META' || node.querySelector('meta')) {
385                                shouldUpdate = true;
386                            }
387                        }
388                    });
389                }
390            });
391            
392            if (shouldUpdate) {
393                console.log('OpenGraph Explorer: Page content changed, updating');
394                debouncedUpdate();
395            }
396        });
397        
398        observer.observe(document.head, {
399            childList: true,
400            subtree: true
401        });
402        
403        console.log('OpenGraph Explorer: Extension initialized successfully');
404    }
405    
406    // Start the extension
407    init();
408})();