Book Exchange Price Monitor - Aseer Alkotb

Monitors book exchange prices every 45 minutes and notifies you of the cheapest deals

Size

28.0 KB

Version

1.1.1

Created

Jan 25, 2026

Updated

9 days ago

1// ==UserScript==
2// @name		Book Exchange Price Monitor - Aseer Alkotb
3// @description		Monitors book exchange prices every 45 minutes and notifies you of the cheapest deals
4// @version		1.1.1
5// @match		https://*.aseeralkotb.com/*
6// @icon		https://cdn.aseeralkotb.com/images/icons/favicons/favicon-32x32.png
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    // ============================================
12    // CONSTANTS & CONFIGURATION
13    // ============================================
14    const CONFIG = {
15        BASE_URL: 'https://www.aseeralkotb.com/ar/book-exchange',
16        TOTAL_PAGES: 5,
17        SCAN_INTERVAL: 45 * 60 * 1000, // 45 minutes in milliseconds
18        PAGE_DELAY: 500, // Delay between page requests (ms)
19        STORAGE_KEYS: {
20            LAST_SCAN: 'lastScanTime',
21            LAST_RESULTS: 'lastResults',
22            MAX_PRICE: 'maxPrice',
23            SETTINGS: 'settings'
24        },
25        DEFAULT_MAX_PRICE: 100,
26        TOP_BOOKS_COUNT: 10
27    };
28
29    // ============================================
30    // UTILITY FUNCTIONS
31    // ============================================
32    
33    /**
34     * Debounce function to limit execution rate
35     */
36    function debounce(func, wait) {
37        let timeout;
38        return function executedFunction(...args) {
39            const later = () => {
40                clearTimeout(timeout);
41                func(...args);
42            };
43            clearTimeout(timeout);
44            timeout = setTimeout(later, wait);
45        };
46    }
47
48    /**
49     * Sleep/delay function
50     */
51    function sleep(ms) {
52        return new Promise(resolve => setTimeout(resolve, ms));
53    }
54
55    /**
56     * Parse Arabic price text to number
57     */
58    function parseArabicPrice(priceText) {
59        if (!priceText) return 0;
60        // Remove Arabic/English currency symbols and spaces
61        const cleaned = priceText.replace(/[^\d٠-٩.]/g, '');
62        // Convert Arabic numerals to English
63        const englishNum = cleaned.replace(/[٠-٩]/g, d => '٠١٢٣٤٥٦٧٨٩'.indexOf(d));
64        return parseFloat(englishNum) || 0;
65    }
66
67    /**
68     * Format timestamp to readable date
69     */
70    function formatDateTime(timestamp) {
71        const date = new Date(timestamp);
72        return date.toLocaleString('ar-EG', {
73            year: 'numeric',
74            month: 'short',
75            day: 'numeric',
76            hour: '2-digit',
77            minute: '2-digit'
78        });
79    }
80
81    // ============================================
82    // SCRAPING FUNCTIONS
83    // ============================================
84
85    /**
86     * Extract books from a page HTML
87     */
88    function extractBooksFromHTML(html, pageNum) {
89        const parser = new DOMParser();
90        const doc = parser.parseFromString(html, 'text/html');
91        const books = [];
92
93        const bookCards = doc.querySelectorAll('.flex.overflow-hidden.flex-col.flex-1.bg-white.rounded-lg');
94        
95        console.log(`[Page ${pageNum}] Found ${bookCards.length} book cards`);
96
97        bookCards.forEach((card, index) => {
98            try {
99                const title = card.querySelector('h1')?.textContent?.trim();
100                const link = card.querySelector('a[href*="/books/"]')?.href;
101                const author = card.querySelector('a[href*="/authors/"] h2')?.textContent?.trim();
102                
103                // Extract final price from meta tag (most reliable)
104                const finalPriceMeta = card.querySelector('meta[itemprop="price"]')?.content;
105                const currency = card.querySelector('meta[itemprop="priceCurrency"]')?.content;
106                
107                // Extract old price
108                const oldPriceText = card.querySelector('.line-through span')?.textContent?.trim();
109                
110                // Extract discount percentage
111                const discountBadge = card.querySelector('[title*="خصم"]');
112                const discount = discountBadge?.textContent?.trim();
113                
114                // Extract image
115                const image = card.querySelector('img')?.src;
116
117                if (title && link && finalPriceMeta) {
118                    const finalPrice = parseFloat(finalPriceMeta);
119                    
120                    books.push({
121                        title,
122                        link,
123                        author: author || 'غير محدد',
124                        final_price: finalPrice,
125                        old_price: oldPriceText || '',
126                        discount: discount || '',
127                        currency: currency || 'EGP',
128                        image: image || '',
129                        page: pageNum
130                    });
131
132                    console.log(`[Page ${pageNum}] Extracted: ${title} - ${finalPrice} ${currency}`);
133                }
134            } catch (error) {
135                console.error(`[Page ${pageNum}] Error extracting book ${index}:`, error);
136            }
137        });
138
139        return books;
140    }
141
142    /**
143     * Extract timer value from page
144     */
145    function extractTimerFromHTML(html) {
146        const parser = new DOMParser();
147        const doc = parser.parseFromString(html, 'text/html');
148        
149        const timerElement = doc.querySelector('.tabular-nums span');
150        const timerText = timerElement?.textContent?.trim();
151
152        if (timerText && timerText.includes(':')) {
153            const parts = timerText.split(':');
154            const minutes = parseInt(parts[0]) || 0;
155            const seconds = parseInt(parts[1]) || 0;
156            const totalSeconds = (minutes * 60) + seconds;
157            
158            console.log(`Timer found: ${timerText} (${totalSeconds} seconds remaining)`);
159            return { timerText, totalSeconds };
160        }
161
162        return null;
163    }
164
165    /**
166     * Fetch a single page
167     */
168    async function fetchPage(pageNum) {
169        const url = `${CONFIG.BASE_URL}?page=${pageNum}`;
170        console.log(`Fetching page ${pageNum}: ${url}`);
171
172        try {
173            const response = await fetch(url, {
174                method: 'GET',
175                headers: {
176                    'Accept': 'text/html',
177                    'User-Agent': navigator.userAgent
178                },
179                credentials: 'include'
180            });
181
182            if (!response.ok) {
183                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
184            }
185
186            const html = await response.text();
187            console.log(`Page ${pageNum} fetched successfully (${html.length} bytes)`);
188            
189            return html;
190        } catch (error) {
191            console.error(`Error fetching page ${pageNum}:`, error);
192            throw error;
193        }
194    }
195
196    /**
197     * Scan all pages and extract books
198     */
199    async function scanAllPages() {
200        console.log('=== Starting full scan of all pages ===');
201        const allBooks = [];
202        let timerInfo = null;
203
204        for (let page = 1; page <= CONFIG.TOTAL_PAGES; page++) {
205            try {
206                const html = await fetchPage(page);
207                
208                // Extract timer from first page only
209                if (page === 1 && !timerInfo) {
210                    timerInfo = extractTimerFromHTML(html);
211                }
212
213                // Extract books
214                const books = extractBooksFromHTML(html, page);
215                allBooks.push(...books);
216
217                console.log(`Page ${page} complete: ${books.length} books extracted`);
218
219                // Delay between pages (except last page)
220                if (page < CONFIG.TOTAL_PAGES) {
221                    await sleep(CONFIG.PAGE_DELAY);
222                }
223            } catch (error) {
224                console.error(`Failed to scan page ${page}:`, error);
225                // Continue with other pages even if one fails
226            }
227        }
228
229        console.log(`=== Scan complete: ${allBooks.length} total books found ===`);
230
231        // Sort by price (ascending)
232        allBooks.sort((a, b) => a.final_price - b.final_price);
233
234        return {
235            books: allBooks,
236            timer: timerInfo,
237            timestamp: Date.now(),
238            totalBooks: allBooks.length
239        };
240    }
241
242    // ============================================
243    // NOTIFICATION FUNCTIONS
244    // ============================================
245
246    /**
247     * Show notification for cheapest books
248     */
249    async function showCheapestBooksNotification(books, count = 5) {
250        const topBooks = books.slice(0, count);
251        
252        const message = topBooks.map((book, index) => 
253            `${index + 1}. ${book.title} - ${book.final_price} ج.م`
254        ).join('\n');
255
256        console.log('Showing cheapest books notification:', message);
257
258        // Create notification UI element
259        createNotificationElement(
260            'أرخص الكتب الآن! 📚',
261            message,
262            'success',
263            10000
264        );
265    }
266
267    /**
268     * Show notification for books under max price
269     */
270    async function showMaxPriceNotification(books, maxPrice) {
271        const message = books.map((book, index) => 
272            `${index + 1}. ${book.title} - ${book.final_price} ج.م\n🔗 ${book.link}`
273        ).join('\n\n');
274
275        console.log(`Showing max price notification (${books.length} books under ${maxPrice} ج.م)`);
276
277        // Create notification UI element
278        createNotificationElement(
279            `🎉 صفقات تحت ${maxPrice} ج.م!`,
280            message,
281            'special',
282            15000
283        );
284    }
285
286    /**
287     * Create notification element in page
288     */
289    function createNotificationElement(title, message, type = 'info', duration = 5000) {
290        // Remove existing notifications
291        const existing = document.querySelectorAll('.aseer-monitor-notification');
292        existing.forEach(el => el.remove());
293
294        const notification = document.createElement('div');
295        notification.className = 'aseer-monitor-notification';
296        notification.style.cssText = `
297            position: fixed;
298            top: 20px;
299            right: 20px;
300            max-width: 400px;
301            background: ${type === 'special' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 
302        type === 'success' ? 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)' : 
303            'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)'};
304            color: white;
305            padding: 20px;
306            border-radius: 12px;
307            box-shadow: 0 10px 40px rgba(0,0,0,0.3);
308            z-index: 999999;
309            font-family: 'Cairo', 'Segoe UI', Tahoma, sans-serif;
310            direction: rtl;
311            animation: slideInRight 0.5s ease-out;
312        `;
313
314        notification.innerHTML = `
315            <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
316                <h3 style="margin: 0; font-size: 18px; font-weight: bold;">${title}</h3>
317                <button onclick="this.parentElement.parentElement.remove()" style="background: rgba(255,255,255,0.3); border: none; color: white; width: 24px; height: 24px; border-radius: 50%; cursor: pointer; font-size: 16px; line-height: 1;">×</button>
318            </div>
319            <div style="font-size: 14px; line-height: 1.6; white-space: pre-line; max-height: 300px; overflow-y: auto;">
320                ${message}
321            </div>
322        `;
323
324        // Add animation styles
325        if (!document.getElementById('aseer-monitor-styles')) {
326            const style = document.createElement('style');
327            style.id = 'aseer-monitor-styles';
328            style.textContent = `
329                @keyframes slideInRight {
330                    from {
331                        transform: translateX(400px);
332                        opacity: 0;
333                    }
334                    to {
335                        transform: translateX(0);
336                        opacity: 1;
337                    }
338                }
339                .aseer-monitor-notification:hover {
340                    transform: scale(1.02);
341                    transition: transform 0.2s ease;
342                }
343            `;
344            document.head.appendChild(style);
345        }
346
347        document.body.appendChild(notification);
348
349        // Auto remove after duration
350        if (duration > 0) {
351            setTimeout(() => {
352                notification.style.animation = 'slideInRight 0.5s ease-out reverse';
353                setTimeout(() => notification.remove(), 500);
354            }, duration);
355        }
356    }
357
358    // ============================================
359    // STORAGE FUNCTIONS
360    // ============================================
361
362    /**
363     * Save scan results to storage
364     */
365    async function saveScanResults(results) {
366        await GM.setValue(CONFIG.STORAGE_KEYS.LAST_RESULTS, JSON.stringify(results));
367        await GM.setValue(CONFIG.STORAGE_KEYS.LAST_SCAN, Date.now());
368        console.log('Scan results saved to storage');
369    }
370
371    /**
372     * Load last scan results from storage
373     */
374    async function loadLastResults() {
375        const resultsJson = await GM.getValue(CONFIG.STORAGE_KEYS.LAST_RESULTS, null);
376        if (resultsJson) {
377            return JSON.parse(resultsJson);
378        }
379        return null;
380    }
381
382    /**
383     * Get max price setting
384     */
385    async function getMaxPrice() {
386        return await GM.getValue(CONFIG.STORAGE_KEYS.MAX_PRICE, CONFIG.DEFAULT_MAX_PRICE);
387    }
388
389    /**
390     * Set max price setting
391     */
392    async function setMaxPrice(price) {
393        await GM.setValue(CONFIG.STORAGE_KEYS.MAX_PRICE, price);
394        console.log(`Max price updated to: ${price}`);
395    }
396
397    /**
398     * Check if results have changed
399     */
400    function hasResultsChanged(oldResults, newResults) {
401        if (!oldResults || !newResults) return true;
402        
403        const oldLinks = oldResults.books.map(b => b.link).sort().join(',');
404        const newLinks = newResults.books.map(b => b.link).sort().join(',');
405        
406        return oldLinks !== newLinks;
407    }
408
409    // ============================================
410    // MAIN SCAN LOGIC
411    // ============================================
412
413    /**
414     * Perform full scan and send notifications
415     */
416    async function performScan() {
417        console.log('=== STARTING BOOK EXCHANGE SCAN ===');
418        
419        try {
420            // Show scanning notification
421            createNotificationElement(
422                '🔍 جاري فحص البورصة...',
423                'يتم الآن فحص جميع الصفحات للعثور على أفضل العروض',
424                'info',
425                3000
426            );
427
428            // Scan all pages
429            const results = await scanAllPages();
430            
431            if (results.books.length === 0) {
432                console.warn('No books found in scan');
433                createNotificationElement(
434                    '⚠️ تنبيه',
435                    'لم يتم العثور على كتب في البورصة',
436                    'info',
437                    5000
438                );
439                return;
440            }
441
442            // Load previous results
443            const lastResults = await loadLastResults();
444            
445            // Check if results changed
446            const changed = hasResultsChanged(lastResults, results);
447            
448            if (!changed) {
449                console.log('Results unchanged, skipping notifications');
450                await saveScanResults(results);
451                return;
452            }
453
454            // Save new results
455            await saveScanResults(results);
456
457            // Show cheapest books notification
458            await showCheapestBooksNotification(results.books, 5);
459
460            // Check for books under max price
461            const maxPrice = await getMaxPrice();
462            const underMaxPrice = results.books.filter(book => book.final_price <= maxPrice);
463            
464            if (underMaxPrice.length > 0) {
465                console.log(`Found ${underMaxPrice.length} books under ${maxPrice} ج.م`);
466                await sleep(1000); // Delay between notifications
467                await showMaxPriceNotification(underMaxPrice, maxPrice);
468            }
469
470            console.log('=== SCAN COMPLETED SUCCESSFULLY ===');
471
472        } catch (error) {
473            console.error('Error during scan:', error);
474            createNotificationElement(
475                '❌ خطأ في الفحص',
476                'حدث خطأ أثناء فحص البورصة. سيتم المحاولة مرة أخرى لاحقاً.',
477                'info',
478                5000
479            );
480        }
481    }
482
483    // ============================================
484    // TIMER MONITORING
485    // ============================================
486
487    /**
488     * Monitor timer and trigger scan when it resets
489     */
490    let lastTimerValue = null;
491    let timerCheckInterval = null;
492
493    function startTimerMonitoring() {
494        console.log('Starting timer monitoring...');
495        
496        timerCheckInterval = setInterval(async () => {
497            const timerElement = document.querySelector('.tabular-nums span');
498            if (!timerElement) return;
499
500            const timerText = timerElement.textContent.trim();
501            
502            // Check if timer reset (went from low value to high value)
503            if (lastTimerValue && timerText) {
504                const [currentMin] = timerText.split(':').map(n => parseInt(n));
505                const [lastMin] = lastTimerValue.split(':').map(n => parseInt(n));
506                
507                // Timer reset detected (e.g., from 00:xx to 44:xx)
508                if (currentMin > 40 && lastMin < 5) {
509                    console.log('Timer reset detected! Triggering scan...');
510                    performScan();
511                }
512            }
513
514            lastTimerValue = timerText;
515        }, 10000); // Check every 10 seconds
516    }
517
518    function stopTimerMonitoring() {
519        if (timerCheckInterval) {
520            clearInterval(timerCheckInterval);
521            timerCheckInterval = null;
522            console.log('Timer monitoring stopped');
523        }
524    }
525
526    // ============================================
527    // PERIODIC SCANNING (FALLBACK)
528    // ============================================
529
530    let periodicScanInterval = null;
531
532    function startPeriodicScanning() {
533        console.log(`Starting periodic scanning (every ${CONFIG.SCAN_INTERVAL / 60000} minutes)...`);
534        
535        periodicScanInterval = setInterval(() => {
536            console.log('Periodic scan triggered');
537            performScan();
538        }, CONFIG.SCAN_INTERVAL);
539    }
540
541    function stopPeriodicScanning() {
542        if (periodicScanInterval) {
543            clearInterval(periodicScanInterval);
544            periodicScanInterval = null;
545            console.log('Periodic scanning stopped');
546        }
547    }
548
549    // ============================================
550    // UI CONTROLS
551    // ============================================
552
553    /**
554     * Create floating control panel
555     */
556    function createControlPanel() {
557        // Check if already exists
558        if (document.getElementById('aseer-monitor-panel')) return;
559
560        const panel = document.createElement('div');
561        panel.id = 'aseer-monitor-panel';
562        panel.style.cssText = `
563            position: fixed;
564            bottom: 20px;
565            left: 20px;
566            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
567            color: white;
568            padding: 15px 20px;
569            border-radius: 12px;
570            box-shadow: 0 8px 32px rgba(0,0,0,0.3);
571            z-index: 999998;
572            font-family: 'Cairo', 'Segoe UI', Tahoma, sans-serif;
573            direction: rtl;
574            min-width: 200px;
575        `;
576
577        panel.innerHTML = `
578            <div style="margin-bottom: 10px;">
579                <strong style="font-size: 16px;">📊 مراقب البورصة</strong>
580            </div>
581            <button id="aseer-scan-now-btn" style="
582                width: 100%;
583                padding: 10px;
584                background: rgba(255,255,255,0.2);
585                border: 2px solid rgba(255,255,255,0.5);
586                color: white;
587                border-radius: 8px;
588                cursor: pointer;
589                font-weight: bold;
590                font-size: 14px;
591                transition: all 0.3s ease;
592                margin-bottom: 8px;
593            ">
594                🔍 فحص الآن
595            </button>
596            <button id="aseer-settings-btn" style="
597                width: 100%;
598                padding: 10px;
599                background: rgba(255,255,255,0.2);
600                border: 2px solid rgba(255,255,255,0.5);
601                color: white;
602                border-radius: 8px;
603                cursor: pointer;
604                font-weight: bold;
605                font-size: 14px;
606                transition: all 0.3s ease;
607                margin-bottom: 8px;
608            ">
609                ⚙️ الإعدادات
610            </button>
611            <div id="aseer-last-scan" style="
612                font-size: 11px;
613                opacity: 0.8;
614                margin-top: 8px;
615                text-align: center;
616            ">
617                آخر فحص: لم يتم بعد
618            </div>
619        `;
620
621        document.body.appendChild(panel);
622
623        // Add hover effects
624        const buttons = panel.querySelectorAll('button');
625        buttons.forEach(btn => {
626            btn.addEventListener('mouseenter', () => {
627                btn.style.background = 'rgba(255,255,255,0.3)';
628                btn.style.transform = 'scale(1.05)';
629            });
630            btn.addEventListener('mouseleave', () => {
631                btn.style.background = 'rgba(255,255,255,0.2)';
632                btn.style.transform = 'scale(1)';
633            });
634        });
635
636        // Scan now button
637        document.getElementById('aseer-scan-now-btn').addEventListener('click', () => {
638            performScan();
639        });
640
641        // Settings button
642        document.getElementById('aseer-settings-btn').addEventListener('click', () => {
643            showSettingsDialog();
644        });
645
646        // Update last scan time
647        updateLastScanDisplay();
648    }
649
650    /**
651     * Update last scan time display
652     */
653    async function updateLastScanDisplay() {
654        const lastScanTime = await GM.getValue(CONFIG.STORAGE_KEYS.LAST_SCAN, null);
655        const display = document.getElementById('aseer-last-scan');
656        
657        if (display && lastScanTime) {
658            display.textContent = `آخر فحص: ${formatDateTime(lastScanTime)}`;
659        }
660    }
661
662    /**
663     * Show settings dialog
664     */
665    async function showSettingsDialog() {
666        const currentMaxPrice = await getMaxPrice();
667
668        const dialog = document.createElement('div');
669        dialog.style.cssText = `
670            position: fixed;
671            top: 50%;
672            left: 50%;
673            transform: translate(-50%, -50%);
674            background: white;
675            padding: 30px;
676            border-radius: 16px;
677            box-shadow: 0 20px 60px rgba(0,0,0,0.4);
678            z-index: 9999999;
679            font-family: 'Cairo', 'Segoe UI', Tahoma, sans-serif;
680            direction: rtl;
681            min-width: 350px;
682        `;
683
684        dialog.innerHTML = `
685            <h2 style="margin: 0 0 20px 0; color: #333; font-size: 22px;">⚙️ إعدادات المراقب</h2>
686            
687            <div style="margin-bottom: 20px;">
688                <label style="display: block; margin-bottom: 8px; color: #555; font-weight: bold;">
689                    الحد الأقصى للسعر (ج.م):
690                </label>
691                <input type="number" id="aseer-max-price-input" value="${currentMaxPrice}" 
692                    style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 8px; font-size: 16px;" 
693                    min="0" step="10">
694                <small style="color: #888; display: block; margin-top: 5px;">
695                    سيتم إرسال إشعار خاص للكتب التي سعرها أقل من أو يساوي هذا الحد
696                </small>
697            </div>
698
699            <div style="display: flex; gap: 10px;">
700                <button id="aseer-save-settings-btn" style="
701                    flex: 1;
702                    padding: 12px;
703                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
704                    border: none;
705                    color: white;
706                    border-radius: 8px;
707                    cursor: pointer;
708                    font-weight: bold;
709                    font-size: 16px;
710                ">
711                    💾 حفظ
712                </button>
713                <button id="aseer-cancel-settings-btn" style="
714                    flex: 1;
715                    padding: 12px;
716                    background: #e0e0e0;
717                    border: none;
718                    color: #333;
719                    border-radius: 8px;
720                    cursor: pointer;
721                    font-weight: bold;
722                    font-size: 16px;
723                ">
724                    ❌ إلغاء
725                </button>
726            </div>
727        `;
728
729        // Backdrop
730        const backdrop = document.createElement('div');
731        backdrop.style.cssText = `
732            position: fixed;
733            top: 0;
734            left: 0;
735            right: 0;
736            bottom: 0;
737            background: rgba(0,0,0,0.5);
738            z-index: 9999998;
739        `;
740
741        document.body.appendChild(backdrop);
742        document.body.appendChild(dialog);
743
744        // Save button
745        document.getElementById('aseer-save-settings-btn').addEventListener('click', async () => {
746            const newMaxPrice = parseInt(document.getElementById('aseer-max-price-input').value);
747            if (newMaxPrice >= 0) {
748                await setMaxPrice(newMaxPrice);
749                createNotificationElement(
750                    '✅ تم الحفظ',
751                    `تم تحديث الحد الأقصى للسعر إلى ${newMaxPrice} ج.م`,
752                    'success',
753                    3000
754                );
755            }
756            backdrop.remove();
757            dialog.remove();
758        });
759
760        // Cancel button
761        document.getElementById('aseer-cancel-settings-btn').addEventListener('click', () => {
762            backdrop.remove();
763            dialog.remove();
764        });
765
766        // Close on backdrop click
767        backdrop.addEventListener('click', () => {
768            backdrop.remove();
769            dialog.remove();
770        });
771    }
772
773    // ============================================
774    // INITIALIZATION
775    // ============================================
776
777    /**
778     * Initialize the extension
779     */
780    async function init() {
781        console.log('=== Book Exchange Monitor Initialized ===');
782        console.log('Current URL:', window.location.href);
783
784        // Check if we're on the book exchange page
785        const isBookExchangePage = window.location.href.includes('/book-exchange');
786
787        if (isBookExchangePage) {
788            console.log('On book exchange page - starting monitoring');
789            
790            // Create control panel
791            createControlPanel();
792            
793            // Start timer monitoring (primary method)
794            startTimerMonitoring();
795            
796            // Start periodic scanning (fallback)
797            startPeriodicScanning();
798
799            // Perform initial scan after 5 seconds
800            setTimeout(() => {
801                console.log('Performing initial scan...');
802                performScan();
803            }, 5000);
804
805        } else {
806            console.log('Not on book exchange page - monitoring disabled');
807            
808            // Still create a minimal control panel for manual scanning
809            createControlPanel();
810        }
811
812        // Update last scan display periodically
813        setInterval(updateLastScanDisplay, 30000); // Every 30 seconds
814    }
815
816    // ============================================
817    // START THE EXTENSION
818    // ============================================
819
820    // Wait for page to be fully loaded
821    if (document.readyState === 'loading') {
822        document.addEventListener('DOMContentLoaded', init);
823    } else {
824        init();
825    }
826
827    // Cleanup on page unload
828    window.addEventListener('beforeunload', () => {
829        stopTimerMonitoring();
830        stopPeriodicScanning();
831    });
832
833    console.log('Book Exchange Monitor script loaded successfully');
834
835})();
Book Exchange Price Monitor - Aseer Alkotb | Robomonkey