MediaGrab Pro - Universal Media Downloader

Professional media downloader with side panel interface. Download images and videos from any website at maximum speed with original filenames.

Size

39.9 KB

Version

1.0.1

Created

Feb 4, 2026

Updated

14 days ago

1// ==UserScript==
2// @name		MediaGrab Pro - Universal Media Downloader
3// @description		Professional media downloader with side panel interface. Download images and videos from any website at maximum speed with original filenames.
4// @version		1.0.1
5// @match		https://*/*
6// @match		http://*/*
7// @icon		https://cococut.net/favicon.ico
8// @grant		GM.getValue
9// @grant		GM.setValue
10// @grant		GM.xmlhttpRequest
11// @grant		GM.openInTab
12// @grant		GM.listValues
13// @grant		GM.deleteValue
14// @connect		*
15// ==/UserScript==
16(function() {
17    'use strict';
18
19    // ============================================
20    // UTILITY FUNCTIONS
21    // ============================================
22
23    function debounce(func, wait) {
24        let timeout;
25        return function executedFunction(...args) {
26            const later = () => {
27                clearTimeout(timeout);
28                func(...args);
29            };
30            clearTimeout(timeout);
31            timeout = setTimeout(later, wait);
32        };
33    }
34
35    function sanitizeFilename(filename) {
36        return filename.replace(/[^a-z0-9_\-\.]/gi, '_').substring(0, 200);
37    }
38
39    function getFileExtension(url, mimeType) {
40        // Try to get extension from URL
41        const urlMatch = url.match(/\.([a-z0-9]+)(?:\?|#|$)/i);
42        if (urlMatch) return urlMatch[1];
43
44        // Fallback to mime type
45        const mimeMap = {
46            'image/jpeg': 'jpg',
47            'image/png': 'png',
48            'image/gif': 'gif',
49            'image/webp': 'webp',
50            'image/svg+xml': 'svg',
51            'video/mp4': 'mp4',
52            'video/webm': 'webm',
53            'video/ogg': 'ogv',
54            'video/quicktime': 'mov'
55        };
56        return mimeMap[mimeType] || 'bin';
57    }
58
59    function formatFileSize(bytes) {
60        if (bytes === 0) return '0 B';
61        const k = 1024;
62        const sizes = ['B', 'KB', 'MB', 'GB'];
63        const i = Math.floor(Math.log(bytes) / Math.log(k));
64        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
65    }
66
67    // ============================================
68    // MEDIA DETECTION
69    // ============================================
70
71    class MediaDetector {
72        constructor() {
73            this.detectedMedia = {
74                images: new Map(),
75                videos: new Map()
76            };
77            this.observer = null;
78        }
79
80        init() {
81            console.log('MediaGrab Pro: Initializing media detector');
82            this.scanPage();
83            this.setupObserver();
84            this.interceptNetworkRequests();
85        }
86
87        scanPage() {
88            // Scan for images
89            const images = document.querySelectorAll('img[src], img[data-src], picture source, [style*="background-image"]');
90            images.forEach(img => this.processImage(img));
91
92            // Scan for videos
93            const videos = document.querySelectorAll('video[src], video source, [data-video-src]');
94            videos.forEach(video => this.processVideo(video));
95
96            console.log(`MediaGrab Pro: Found ${this.detectedMedia.images.size} images and ${this.detectedMedia.videos.size} videos`);
97        }
98
99        processImage(element) {
100            let src = element.src || element.dataset.src || element.getAttribute('data-lazy-src');
101            
102            // Check for background images
103            if (!src && element.style.backgroundImage) {
104                const match = element.style.backgroundImage.match(/url\(['"]?([^'"]+)['"]?\)/);
105                if (match) src = match[1];
106            }
107
108            if (src && src.startsWith('http') && !src.includes('data:image')) {
109                const url = new URL(src, window.location.href).href;
110                if (!this.detectedMedia.images.has(url)) {
111                    this.detectedMedia.images.set(url, {
112                        url: url,
113                        type: 'image',
114                        element: element,
115                        filename: this.extractFilename(url, 'image'),
116                        timestamp: Date.now()
117                    });
118                }
119            }
120        }
121
122        processVideo(element) {
123            let src = element.src || element.dataset.src || element.dataset.videoSrc;
124            
125            // Check for source elements
126            if (!src && element.tagName === 'SOURCE') {
127                src = element.src;
128            }
129            
130            // Check parent video element
131            if (!src && element.parentElement && element.parentElement.tagName === 'VIDEO') {
132                src = element.parentElement.src;
133            }
134
135            if (src && src.startsWith('http')) {
136                const url = new URL(src, window.location.href).href;
137                if (!this.detectedMedia.videos.has(url)) {
138                    this.detectedMedia.videos.set(url, {
139                        url: url,
140                        type: 'video',
141                        element: element,
142                        filename: this.extractFilename(url, 'video'),
143                        timestamp: Date.now()
144                    });
145                }
146            }
147        }
148
149        extractFilename(url, type) {
150            try {
151                // Try to get filename from URL
152                const urlObj = new URL(url);
153                let filename = urlObj.pathname.split('/').pop();
154                
155                // If no filename, try to get from page title
156                if (!filename || filename.length < 3) {
157                    const pageTitle = document.title.substring(0, 50);
158                    const timestamp = Date.now();
159                    filename = `${sanitizeFilename(pageTitle)}_${timestamp}`;
160                }
161                
162                // Ensure extension
163                if (!filename.match(/\.[a-z0-9]+$/i)) {
164                    filename += type === 'video' ? '.mp4' : '.jpg';
165                }
166                
167                return sanitizeFilename(filename);
168            } catch (e) {
169                return `media_${Date.now()}.${type === 'video' ? 'mp4' : 'jpg'}`;
170            }
171        }
172
173        setupObserver() {
174            const debouncedScan = debounce(() => this.scanPage(), 1000);
175            
176            this.observer = new MutationObserver(debouncedScan);
177            this.observer.observe(document.body, {
178                childList: true,
179                subtree: true,
180                attributes: true,
181                attributeFilter: ['src', 'data-src', 'style']
182            });
183        }
184
185        interceptNetworkRequests() {
186            // Intercept fetch requests
187            const originalFetch = window.fetch;
188            window.fetch = async (...args) => {
189                const response = await originalFetch(...args);
190                const url = typeof args[0] === 'string' ? args[0] : args[0].url;
191                
192                if (response.ok) {
193                    const contentType = response.headers.get('content-type');
194                    if (contentType) {
195                        if (contentType.includes('image/')) {
196                            this.detectedMedia.images.set(url, {
197                                url: url,
198                                type: 'image',
199                                filename: this.extractFilename(url, 'image'),
200                                timestamp: Date.now()
201                            });
202                        } else if (contentType.includes('video/')) {
203                            this.detectedMedia.videos.set(url, {
204                                url: url,
205                                type: 'video',
206                                filename: this.extractFilename(url, 'video'),
207                                timestamp: Date.now()
208                            });
209                        }
210                    }
211                }
212                
213                return response;
214            };
215        }
216
217        getDetectedMedia() {
218            return {
219                images: Array.from(this.detectedMedia.images.values()),
220                videos: Array.from(this.detectedMedia.videos.values())
221            };
222        }
223    }
224
225    // ============================================
226    // DOWNLOAD MANAGER
227    // ============================================
228
229    class DownloadManager {
230        constructor() {
231            this.downloads = new Map();
232            this.activeDownloads = 0;
233            this.maxConcurrent = 10; // Maximum concurrent downloads for speed
234        }
235
236        async download(mediaItem) {
237            const downloadId = `download_${Date.now()}_${Math.random()}`;
238            
239            const downloadInfo = {
240                id: downloadId,
241                url: mediaItem.url,
242                filename: mediaItem.filename,
243                status: 'pending',
244                progress: 0,
245                size: 0,
246                downloadedSize: 0,
247                startTime: Date.now(),
248                error: null
249            };
250
251            this.downloads.set(downloadId, downloadInfo);
252            this.notifyUpdate(downloadId);
253
254            try {
255                await this.performDownload(downloadId, mediaItem);
256            } catch (error) {
257                console.error('MediaGrab Pro: Download failed', error);
258                downloadInfo.status = 'failed';
259                downloadInfo.error = error.message;
260                this.notifyUpdate(downloadId);
261            }
262
263            return downloadId;
264        }
265
266        async performDownload(downloadId, mediaItem) {
267            const downloadInfo = this.downloads.get(downloadId);
268            downloadInfo.status = 'downloading';
269            this.activeDownloads++;
270            this.notifyUpdate(downloadId);
271
272            return new Promise((resolve, reject) => {
273                GM.xmlhttpRequest({
274                    method: 'GET',
275                    url: mediaItem.url,
276                    responseType: 'blob',
277                    onprogress: (progress) => {
278                        if (progress.lengthComputable) {
279                            downloadInfo.progress = (progress.loaded / progress.total) * 100;
280                            downloadInfo.size = progress.total;
281                            downloadInfo.downloadedSize = progress.loaded;
282                            this.notifyUpdate(downloadId);
283                        }
284                    },
285                    onload: (response) => {
286                        try {
287                            const blob = response.response;
288                            const url = URL.createObjectURL(blob);
289                            
290                            // Create download link
291                            const a = document.createElement('a');
292                            a.href = url;
293                            a.download = mediaItem.filename;
294                            document.body.appendChild(a);
295                            a.click();
296                            document.body.removeChild(a);
297                            
298                            // Cleanup
299                            setTimeout(() => URL.revokeObjectURL(url), 100);
300                            
301                            downloadInfo.status = 'completed';
302                            downloadInfo.progress = 100;
303                            this.activeDownloads--;
304                            this.notifyUpdate(downloadId);
305                            
306                            console.log(`MediaGrab Pro: Downloaded ${mediaItem.filename}`);
307                            resolve();
308                        } catch (error) {
309                            reject(error);
310                        }
311                    },
312                    onerror: (error) => {
313                        downloadInfo.status = 'failed';
314                        downloadInfo.error = 'Network error';
315                        this.activeDownloads--;
316                        this.notifyUpdate(downloadId);
317                        reject(new Error('Network error'));
318                    },
319                    ontimeout: () => {
320                        downloadInfo.status = 'failed';
321                        downloadInfo.error = 'Timeout';
322                        this.activeDownloads--;
323                        this.notifyUpdate(downloadId);
324                        reject(new Error('Timeout'));
325                    }
326                });
327            });
328        }
329
330        async downloadBatch(mediaItems) {
331            console.log(`MediaGrab Pro: Starting batch download of ${mediaItems.length} items`);
332            const downloadPromises = mediaItems.map(item => this.download(item));
333            return Promise.allSettled(downloadPromises);
334        }
335
336        notifyUpdate(downloadId) {
337            const event = new CustomEvent('mediagrab-download-update', {
338                detail: {
339                    downloadId: downloadId,
340                    download: this.downloads.get(downloadId)
341                }
342            });
343            window.dispatchEvent(event);
344        }
345
346        getDownloads() {
347            return Array.from(this.downloads.values());
348        }
349
350        clearCompleted() {
351            for (const [id, download] of this.downloads.entries()) {
352                if (download.status === 'completed') {
353                    this.downloads.delete(id);
354                }
355            }
356        }
357    }
358
359    // ============================================
360    // SIDE PANEL UI
361    // ============================================
362
363    class SidePanelUI {
364        constructor(mediaDetector, downloadManager) {
365            this.mediaDetector = mediaDetector;
366            this.downloadManager = downloadManager;
367            this.isOpen = false;
368            this.currentTab = 'images';
369            this.panel = null;
370        }
371
372        init() {
373            this.createToggleButton();
374            this.createPanel();
375            this.setupEventListeners();
376            console.log('MediaGrab Pro: Side panel UI initialized');
377        }
378
379        createToggleButton() {
380            const button = document.createElement('button');
381            button.id = 'mediagrab-toggle-btn';
382            button.innerHTML = '📥';
383            button.title = 'MediaGrab Pro';
384            
385            const style = document.createElement('style');
386            style.textContent = `
387                #mediagrab-toggle-btn {
388                    position: fixed;
389                    top: 50%;
390                    right: 0;
391                    transform: translateY(-50%);
392                    width: 50px;
393                    height: 50px;
394                    background: linear-gradient(135deg, #9b59b6 0%, #e91e63 100%);
395                    border: none;
396                    border-radius: 10px 0 0 10px;
397                    color: white;
398                    font-size: 24px;
399                    cursor: pointer;
400                    z-index: 999999;
401                    box-shadow: -2px 2px 10px rgba(0,0,0,0.3);
402                    transition: all 0.3s ease;
403                }
404                #mediagrab-toggle-btn:hover {
405                    width: 60px;
406                    box-shadow: -4px 4px 15px rgba(0,0,0,0.4);
407                }
408            `;
409            
410            document.head.appendChild(style);
411            document.body.appendChild(button);
412            
413            button.addEventListener('click', () => this.togglePanel());
414        }
415
416        createPanel() {
417            const panel = document.createElement('div');
418            panel.id = 'mediagrab-panel';
419            panel.innerHTML = `
420                <div class="mediagrab-header">
421                    <h2>MediaGrab Pro</h2>
422                    <button class="mediagrab-close-btn"></button>
423                </div>
424                
425                <div class="mediagrab-tabs">
426                    <button class="mediagrab-tab active" data-tab="images">
427                        <span class="tab-icon">🖼️</span>
428                        <span class="tab-label">Images</span>
429                        <span class="tab-count" id="images-count">0</span>
430                    </button>
431                    <button class="mediagrab-tab" data-tab="videos">
432                        <span class="tab-icon">🎬</span>
433                        <span class="tab-label">Videos</span>
434                        <span class="tab-count" id="videos-count">0</span>
435                    </button>
436                    <button class="mediagrab-tab" data-tab="downloads">
437                        <span class="tab-icon">📥</span>
438                        <span class="tab-label">Downloads</span>
439                        <span class="tab-count" id="downloads-count">0</span>
440                    </button>
441                </div>
442
443                <div class="mediagrab-content">
444                    <div class="mediagrab-tab-content active" id="images-content">
445                        <div class="mediagrab-actions">
446                            <button class="mediagrab-btn primary" id="download-all-images">
447                                Download All Images
448                            </button>
449                            <button class="mediagrab-btn secondary" id="refresh-images">
450                                Refresh
451                            </button>
452                        </div>
453                        <div class="mediagrab-media-grid" id="images-grid"></div>
454                    </div>
455
456                    <div class="mediagrab-tab-content" id="videos-content">
457                        <div class="mediagrab-actions">
458                            <button class="mediagrab-btn primary" id="download-all-videos">
459                                Download All Videos
460                            </button>
461                            <button class="mediagrab-btn secondary" id="refresh-videos">
462                                Refresh
463                            </button>
464                        </div>
465                        <div class="mediagrab-media-list" id="videos-list"></div>
466                    </div>
467
468                    <div class="mediagrab-tab-content" id="downloads-content">
469                        <div class="mediagrab-actions">
470                            <button class="mediagrab-btn secondary" id="clear-completed">
471                                Clear Completed
472                            </button>
473                        </div>
474                        <div class="mediagrab-downloads-list" id="downloads-list"></div>
475                    </div>
476                </div>
477            `;
478
479            this.addStyles();
480            document.body.appendChild(panel);
481            this.panel = panel;
482        }
483
484        addStyles() {
485            const style = document.createElement('style');
486            style.textContent = `
487                #mediagrab-panel {
488                    position: fixed;
489                    top: 0;
490                    right: -450px;
491                    width: 450px;
492                    height: 100vh;
493                    background: linear-gradient(180deg, #f8f9fa 0%, #e9ecef 100%);
494                    box-shadow: -5px 0 20px rgba(0,0,0,0.3);
495                    z-index: 999998;
496                    transition: right 0.3s ease;
497                    display: flex;
498                    flex-direction: column;
499                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
500                }
501
502                #mediagrab-panel.open {
503                    right: 0;
504                }
505
506                .mediagrab-header {
507                    background: linear-gradient(135deg, #9b59b6 0%, #e91e63 50%, #89b5f5 100%);
508                    color: white;
509                    padding: 20px;
510                    display: flex;
511                    justify-content: space-between;
512                    align-items: center;
513                    box-shadow: 0 2px 10px rgba(0,0,0,0.2);
514                }
515
516                .mediagrab-header h2 {
517                    margin: 0;
518                    font-size: 24px;
519                    font-weight: 600;
520                }
521
522                .mediagrab-close-btn {
523                    background: rgba(255,255,255,0.2);
524                    border: none;
525                    color: white;
526                    width: 35px;
527                    height: 35px;
528                    border-radius: 50%;
529                    font-size: 20px;
530                    cursor: pointer;
531                    transition: all 0.2s;
532                }
533
534                .mediagrab-close-btn:hover {
535                    background: rgba(255,255,255,0.3);
536                    transform: rotate(90deg);
537                }
538
539                .mediagrab-tabs {
540                    display: flex;
541                    background: white;
542                    border-bottom: 2px solid #dee2e6;
543                }
544
545                .mediagrab-tab {
546                    flex: 1;
547                    padding: 15px 10px;
548                    background: white;
549                    border: none;
550                    cursor: pointer;
551                    display: flex;
552                    flex-direction: column;
553                    align-items: center;
554                    gap: 5px;
555                    transition: all 0.2s;
556                    position: relative;
557                }
558
559                .mediagrab-tab:hover {
560                    background: #f8f9fa;
561                }
562
563                .mediagrab-tab.active {
564                    background: linear-gradient(135deg, #9b59b6 0%, #e91e63 100%);
565                    color: white;
566                }
567
568                .mediagrab-tab .tab-icon {
569                    font-size: 24px;
570                }
571
572                .mediagrab-tab .tab-label {
573                    font-size: 12px;
574                    font-weight: 500;
575                }
576
577                .mediagrab-tab .tab-count {
578                    position: absolute;
579                    top: 5px;
580                    right: 5px;
581                    background: #e91e63;
582                    color: white;
583                    border-radius: 10px;
584                    padding: 2px 6px;
585                    font-size: 10px;
586                    font-weight: bold;
587                }
588
589                .mediagrab-tab.active .tab-count {
590                    background: rgba(255,255,255,0.3);
591                }
592
593                .mediagrab-content {
594                    flex: 1;
595                    overflow-y: auto;
596                    padding: 15px;
597                }
598
599                .mediagrab-tab-content {
600                    display: none;
601                }
602
603                .mediagrab-tab-content.active {
604                    display: block;
605                }
606
607                .mediagrab-actions {
608                    display: flex;
609                    gap: 10px;
610                    margin-bottom: 15px;
611                }
612
613                .mediagrab-btn {
614                    flex: 1;
615                    padding: 12px 20px;
616                    border: none;
617                    border-radius: 8px;
618                    font-size: 14px;
619                    font-weight: 600;
620                    cursor: pointer;
621                    transition: all 0.2s;
622                }
623
624                .mediagrab-btn.primary {
625                    background: linear-gradient(135deg, #9b59b6 0%, #e91e63 100%);
626                    color: white;
627                }
628
629                .mediagrab-btn.primary:hover {
630                    transform: translateY(-2px);
631                    box-shadow: 0 4px 12px rgba(155, 89, 182, 0.4);
632                }
633
634                .mediagrab-btn.secondary {
635                    background: #89b5f5;
636                    color: white;
637                }
638
639                .mediagrab-btn.secondary:hover {
640                    background: #6fa3f3;
641                    transform: translateY(-2px);
642                }
643
644                .mediagrab-media-grid {
645                    display: grid;
646                    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
647                    gap: 10px;
648                }
649
650                .mediagrab-media-item {
651                    position: relative;
652                    aspect-ratio: 1;
653                    border-radius: 8px;
654                    overflow: hidden;
655                    cursor: pointer;
656                    transition: all 0.2s;
657                    background: white;
658                    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
659                }
660
661                .mediagrab-media-item:hover {
662                    transform: scale(1.05);
663                    box-shadow: 0 4px 16px rgba(0,0,0,0.2);
664                }
665
666                .mediagrab-media-item img {
667                    width: 100%;
668                    height: 100%;
669                    object-fit: cover;
670                }
671
672                .mediagrab-media-item .download-overlay {
673                    position: absolute;
674                    top: 0;
675                    left: 0;
676                    right: 0;
677                    bottom: 0;
678                    background: rgba(155, 89, 182, 0.9);
679                    display: flex;
680                    align-items: center;
681                    justify-content: center;
682                    opacity: 0;
683                    transition: opacity 0.2s;
684                }
685
686                .mediagrab-media-item:hover .download-overlay {
687                    opacity: 1;
688                }
689
690                .mediagrab-media-item .download-overlay span {
691                    color: white;
692                    font-size: 32px;
693                }
694
695                .mediagrab-media-list {
696                    display: flex;
697                    flex-direction: column;
698                    gap: 10px;
699                }
700
701                .mediagrab-video-item {
702                    background: white;
703                    border-radius: 8px;
704                    padding: 15px;
705                    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
706                    transition: all 0.2s;
707                }
708
709                .mediagrab-video-item:hover {
710                    box-shadow: 0 4px 16px rgba(0,0,0,0.2);
711                }
712
713                .mediagrab-video-item .video-info {
714                    display: flex;
715                    align-items: center;
716                    gap: 10px;
717                    margin-bottom: 10px;
718                }
719
720                .mediagrab-video-item .video-icon {
721                    font-size: 32px;
722                }
723
724                .mediagrab-video-item .video-name {
725                    flex: 1;
726                    font-size: 14px;
727                    font-weight: 500;
728                    color: #333;
729                    word-break: break-all;
730                }
731
732                .mediagrab-video-item .video-download-btn {
733                    background: linear-gradient(135deg, #9b59b6 0%, #e91e63 100%);
734                    color: white;
735                    border: none;
736                    padding: 8px 16px;
737                    border-radius: 6px;
738                    cursor: pointer;
739                    font-weight: 600;
740                    transition: all 0.2s;
741                }
742
743                .mediagrab-video-item .video-download-btn:hover {
744                    transform: translateY(-2px);
745                    box-shadow: 0 4px 12px rgba(155, 89, 182, 0.4);
746                }
747
748                .mediagrab-downloads-list {
749                    display: flex;
750                    flex-direction: column;
751                    gap: 10px;
752                }
753
754                .mediagrab-download-item {
755                    background: white;
756                    border-radius: 8px;
757                    padding: 15px;
758                    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
759                }
760
761                .mediagrab-download-item .download-header {
762                    display: flex;
763                    justify-content: space-between;
764                    align-items: center;
765                    margin-bottom: 10px;
766                }
767
768                .mediagrab-download-item .download-name {
769                    font-size: 14px;
770                    font-weight: 500;
771                    color: #333;
772                    word-break: break-all;
773                }
774
775                .mediagrab-download-item .download-status {
776                    padding: 4px 8px;
777                    border-radius: 4px;
778                    font-size: 11px;
779                    font-weight: 600;
780                    text-transform: uppercase;
781                }
782
783                .mediagrab-download-item .download-status.completed {
784                    background: #d4edda;
785                    color: #155724;
786                }
787
788                .mediagrab-download-item .download-status.downloading {
789                    background: #cce5ff;
790                    color: #004085;
791                }
792
793                .mediagrab-download-item .download-status.failed {
794                    background: #f8d7da;
795                    color: #721c24;
796                }
797
798                .mediagrab-download-item .download-progress {
799                    width: 100%;
800                    height: 6px;
801                    background: #e9ecef;
802                    border-radius: 3px;
803                    overflow: hidden;
804                    margin-top: 8px;
805                }
806
807                .mediagrab-download-item .download-progress-bar {
808                    height: 100%;
809                    background: linear-gradient(90deg, #9b59b6 0%, #e91e63 100%);
810                    transition: width 0.3s ease;
811                }
812
813                .mediagrab-download-item .download-info {
814                    display: flex;
815                    justify-content: space-between;
816                    margin-top: 8px;
817                    font-size: 12px;
818                    color: #6c757d;
819                }
820
821                .mediagrab-empty-state {
822                    text-align: center;
823                    padding: 40px 20px;
824                    color: #6c757d;
825                }
826
827                .mediagrab-empty-state .empty-icon {
828                    font-size: 64px;
829                    margin-bottom: 15px;
830                }
831
832                .mediagrab-empty-state .empty-text {
833                    font-size: 16px;
834                    font-weight: 500;
835                }
836
837                /* Scrollbar styling */
838                .mediagrab-content::-webkit-scrollbar {
839                    width: 8px;
840                }
841
842                .mediagrab-content::-webkit-scrollbar-track {
843                    background: #f1f1f1;
844                }
845
846                .mediagrab-content::-webkit-scrollbar-thumb {
847                    background: linear-gradient(180deg, #9b59b6 0%, #e91e63 100%);
848                    border-radius: 4px;
849                }
850
851                .mediagrab-content::-webkit-scrollbar-thumb:hover {
852                    background: linear-gradient(180deg, #8e44ad 0%, #c2185b 100%);
853                }
854            `;
855            
856            document.head.appendChild(style);
857        }
858
859        setupEventListeners() {
860            // Close button
861            this.panel.querySelector('.mediagrab-close-btn').addEventListener('click', () => {
862                this.togglePanel();
863            });
864
865            // Tab switching
866            this.panel.querySelectorAll('.mediagrab-tab').forEach(tab => {
867                tab.addEventListener('click', () => {
868                    const tabName = tab.dataset.tab;
869                    this.switchTab(tabName);
870                });
871            });
872
873            // Action buttons
874            document.getElementById('download-all-images').addEventListener('click', () => {
875                this.downloadAllImages();
876            });
877
878            document.getElementById('download-all-videos').addEventListener('click', () => {
879                this.downloadAllVideos();
880            });
881
882            document.getElementById('refresh-images').addEventListener('click', () => {
883                this.refreshMedia();
884            });
885
886            document.getElementById('refresh-videos').addEventListener('click', () => {
887                this.refreshMedia();
888            });
889
890            document.getElementById('clear-completed').addEventListener('click', () => {
891                this.downloadManager.clearCompleted();
892                this.updateDownloadsTab();
893            });
894
895            // Listen for download updates
896            window.addEventListener('mediagrab-download-update', () => {
897                this.updateDownloadsTab();
898            });
899
900            // Auto-refresh media periodically
901            setInterval(() => {
902                if (this.isOpen) {
903                    this.updateMediaCounts();
904                }
905            }, 3000);
906        }
907
908        togglePanel() {
909            this.isOpen = !this.isOpen;
910            this.panel.classList.toggle('open', this.isOpen);
911            
912            if (this.isOpen) {
913                this.refreshMedia();
914            }
915        }
916
917        switchTab(tabName) {
918            this.currentTab = tabName;
919            
920            // Update tab buttons
921            this.panel.querySelectorAll('.mediagrab-tab').forEach(tab => {
922                tab.classList.toggle('active', tab.dataset.tab === tabName);
923            });
924
925            // Update tab content
926            this.panel.querySelectorAll('.mediagrab-tab-content').forEach(content => {
927                content.classList.toggle('active', content.id === `${tabName}-content`);
928            });
929
930            // Refresh content
931            if (tabName === 'images') {
932                this.updateImagesTab();
933            } else if (tabName === 'videos') {
934                this.updateVideosTab();
935            } else if (tabName === 'downloads') {
936                this.updateDownloadsTab();
937            }
938        }
939
940        refreshMedia() {
941            this.mediaDetector.scanPage();
942            this.updateMediaCounts();
943            
944            if (this.currentTab === 'images') {
945                this.updateImagesTab();
946            } else if (this.currentTab === 'videos') {
947                this.updateVideosTab();
948            }
949        }
950
951        updateMediaCounts() {
952            const media = this.mediaDetector.getDetectedMedia();
953            document.getElementById('images-count').textContent = media.images.length;
954            document.getElementById('videos-count').textContent = media.videos.length;
955            document.getElementById('downloads-count').textContent = this.downloadManager.getDownloads().length;
956        }
957
958        updateImagesTab() {
959            const media = this.mediaDetector.getDetectedMedia();
960            const grid = document.getElementById('images-grid');
961            
962            if (media.images.length === 0) {
963                grid.innerHTML = `
964                    <div class="mediagrab-empty-state">
965                        <div class="empty-icon">🖼️</div>
966                        <div class="empty-text">No images detected on this page</div>
967                    </div>
968                `;
969                return;
970            }
971
972            grid.innerHTML = media.images.map(img => `
973                <div class="mediagrab-media-item" data-url="${img.url}" data-filename="${img.filename}">
974                    <img src="${img.url}" alt="${img.filename}" loading="lazy">
975                    <div class="download-overlay">
976                        <span>⬇️</span>
977                    </div>
978                </div>
979            `).join('');
980
981            // Add click handlers
982            grid.querySelectorAll('.mediagrab-media-item').forEach(item => {
983                item.addEventListener('click', () => {
984                    const url = item.dataset.url;
985                    const filename = item.dataset.filename;
986                    this.downloadManager.download({ url, filename, type: 'image' });
987                });
988            });
989        }
990
991        updateVideosTab() {
992            const media = this.mediaDetector.getDetectedMedia();
993            const list = document.getElementById('videos-list');
994            
995            if (media.videos.length === 0) {
996                list.innerHTML = `
997                    <div class="mediagrab-empty-state">
998                        <div class="empty-icon">🎬</div>
999                        <div class="empty-text">No videos detected on this page</div>
1000                    </div>
1001                `;
1002                return;
1003            }
1004
1005            list.innerHTML = media.videos.map(video => `
1006                <div class="mediagrab-video-item">
1007                    <div class="video-info">
1008                        <div class="video-icon">🎬</div>
1009                        <div class="video-name">${video.filename}</div>
1010                        <button class="video-download-btn" data-url="${video.url}" data-filename="${video.filename}">
1011                            Download
1012                        </button>
1013                    </div>
1014                </div>
1015            `).join('');
1016
1017            // Add click handlers
1018            list.querySelectorAll('.video-download-btn').forEach(btn => {
1019                btn.addEventListener('click', () => {
1020                    const url = btn.dataset.url;
1021                    const filename = btn.dataset.filename;
1022                    this.downloadManager.download({ url, filename, type: 'video' });
1023                });
1024            });
1025        }
1026
1027        updateDownloadsTab() {
1028            const downloads = this.downloadManager.getDownloads();
1029            const list = document.getElementById('downloads-list');
1030            
1031            if (downloads.length === 0) {
1032                list.innerHTML = `
1033                    <div class="mediagrab-empty-state">
1034                        <div class="empty-icon">📥</div>
1035                        <div class="empty-text">No downloads yet</div>
1036                    </div>
1037                `;
1038                return;
1039            }
1040
1041            list.innerHTML = downloads.map(download => `
1042                <div class="mediagrab-download-item">
1043                    <div class="download-header">
1044                        <div class="download-name">${download.filename}</div>
1045                        <div class="download-status ${download.status}">${download.status}</div>
1046                    </div>
1047                    ${download.status === 'downloading' ? `
1048                        <div class="download-progress">
1049                            <div class="download-progress-bar" style="width: ${download.progress}%"></div>
1050                        </div>
1051                        <div class="download-info">
1052                            <span>${Math.round(download.progress)}%</span>
1053                            <span>${formatFileSize(download.downloadedSize)} / ${formatFileSize(download.size)}</span>
1054                        </div>
1055                    ` : ''}
1056                    ${download.status === 'failed' ? `
1057                        <div class="download-info">
1058                            <span style="color: #dc3545;">${download.error}</span>
1059                        </div>
1060                    ` : ''}
1061                </div>
1062            `).join('');
1063
1064            this.updateMediaCounts();
1065        }
1066
1067        async downloadAllImages() {
1068            const media = this.mediaDetector.getDetectedMedia();
1069            if (media.images.length === 0) {
1070                alert('No images to download');
1071                return;
1072            }
1073
1074            console.log(`MediaGrab Pro: Downloading ${media.images.length} images`);
1075            await this.downloadManager.downloadBatch(media.images);
1076            this.switchTab('downloads');
1077        }
1078
1079        async downloadAllVideos() {
1080            const media = this.mediaDetector.getDetectedMedia();
1081            if (media.videos.length === 0) {
1082                alert('No videos to download');
1083                return;
1084            }
1085
1086            console.log(`MediaGrab Pro: Downloading ${media.videos.length} videos`);
1087            await this.downloadManager.downloadBatch(media.videos);
1088            this.switchTab('downloads');
1089        }
1090    }
1091
1092    // ============================================
1093    // INITIALIZATION
1094    // ============================================
1095
1096    function init() {
1097        console.log('MediaGrab Pro: Starting initialization');
1098        
1099        // Wait for page to be ready
1100        if (document.readyState === 'loading') {
1101            document.addEventListener('DOMContentLoaded', init);
1102            return;
1103        }
1104
1105        // Initialize components
1106        const mediaDetector = new MediaDetector();
1107        const downloadManager = new DownloadManager();
1108        const sidePanelUI = new SidePanelUI(mediaDetector, downloadManager);
1109
1110        // Start the extension
1111        setTimeout(() => {
1112            mediaDetector.init();
1113            sidePanelUI.init();
1114            console.log('MediaGrab Pro: Fully initialized and ready');
1115        }, 1000);
1116    }
1117
1118    // Start the extension
1119    init();
1120
1121})();
MediaGrab Pro - Universal Media Downloader | Robomonkey