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})();