您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Completely transforms OLX listings with beautiful background images and modern design
// ==UserScript== // @name OLX Premium Redesign with Background Images // @namespace http://tampermonkey.net/ // @version 2.0 // @description Completely transforms OLX listings with beautiful background images and modern design // @author im tired of this // @match https://www.olx.bg/* // @grant GM_xmlhttpRequest // @connect olx.bg // @run-at document-start // ==/UserScript== (function() { 'use strict'; // Cache for fetched data and processed pages/cards const dataCache = {}; const processedPages = new Set(); const processedCards = new Set(); // Track current page and max page let currentPage = 1; let maxPage = 1; let isLoadingMorePages = false; // Placeholder image URL const PLACEHOLDER_IMAGE = "https://www.olx.bg/app/static/media/no_thumbnail.15f456ec5.svg"; // Settings stored in localStorage const getSettings = () => { const defaultSettings = { hidePromoted: false }; try { const stored = localStorage.getItem('olx-enhanced-settings'); return stored ? JSON.parse(stored) : defaultSettings; } catch (e) { console.error('Error loading settings:', e); return defaultSettings; } }; const saveSettings = (settings) => { try { localStorage.setItem('olx-enhanced-settings', JSON.stringify(settings)); } catch (e) { console.error('Error saving settings:', e); } }; // Add our stylesheet const addStyles = () => { if (document.getElementById('olx-premium-styles')) return; const style = document.createElement('style'); style.id = 'olx-premium-styles'; style.textContent = ` /* Enhanced card with background image */ .olx-card { padding: 20px; border-radius: 12px; margin-bottom: 20px; position: relative; overflow: hidden; box-shadow: 0 4px 12px rgba(0, 40, 44, 0.15); transition: transform 0.2s, box-shadow 0.2s; background-color: white; z-index: 1; min-height: 200px; } .olx-card:hover { transform: translateY(-3px); box-shadow: 0 6px 16px rgba(0, 40, 44, 0.2); } /* Background image overlay */ .olx-bg-image { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.12; z-index: -1; filter: blur(3px); transform: scale(1.1); } /* Main card content */ .olx-content { position: relative; display: flex; flex-direction: column; height: 100%; z-index: 2; } /* Top section with tags, title, date and price */ .olx-top { display: flex; flex-wrap: wrap; justify-content: space-between; margin-bottom: 15px; } /* Tags and title container */ .olx-title-section { flex: 1; padding-right: 20px; } /* Tags row */ .olx-tags { display: flex; gap: 8px; margin-bottom: 8px; align-items: center; } /* Individual tags */ .olx-tag { display: inline-flex; align-items: center; height: 24px; padding: 0 12px; border-radius: 20px; font-size: 13px; font-weight: bold; text-transform: uppercase; } .olx-condition-tag { background: #23e5db; color: white; } .olx-promoted-tag { background: #ff9800; color: white; } .olx-delivery-tag { background: #4caf50; color: white; display: inline-flex; align-items: center; gap: 5px; } /* Title */ .olx-title { font-size: 25px; font-weight: bold; color: #002f34; line-height: 1.3; } /* Price and date section */ .olx-price-section { display: flex; flex-direction: column; align-items: flex-end; } /* Date above price */ .olx-date { font-size: 16px; color: #406367; margin-bottom: 6px; } /* Price */ .olx-price { font-size: 40px; font-weight: bold; color: #005f64; } /* Middle section with thumbnail gallery */ .olx-middle { display: flex; margin: 15px 0; } /* Gallery */ .olx-gallery { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 15px; justify-content: flex-start; } .olx-thumb { width: 100px; height: 100px; border-radius: 8px; border: 2px solid #e0e0e0; cursor: pointer; object-fit: cover; transition: transform 0.15s, border-color 0.15s; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); } .olx-thumb:hover { transform: scale(1.05); border-color: #23e5db; } /* Description */ .olx-desc { font-size: 16px; line-height: 1.5; color: #406367; padding: 15px; background: rgba(248, 251, 251, 0.5); border-radius: 8px; margin-bottom: 15px; overflow: hidden; max-height: 145px; position: relative; transition: max-height 0.3s; border: 1px solid rgba(238, 242, 242, 0.8); } .olx-desc.expanded { max-height: 1000px; } .olx-desc.short { max-height: none; } .olx-desc:not(.expanded):not(.short)::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 25px; background: linear-gradient(transparent, rgba(248, 251, 251, 0.9)); } .olx-toggle { color: #23e5db; cursor: pointer; font-size: 15px; margin-bottom: 15px; display: inline-block; user-select: none; font-weight: bold; } .olx-toggle:hover { text-decoration: underline; } /* Bottom section with actions and location */ .olx-bottom { display: flex; justify-content: space-between; align-items: center; margin-top: auto; } /* Action buttons */ .olx-actions { display: flex; gap: 10px; } .olx-follow { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 8px 15px; background: #f0f4f4; color: #406367; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: bold; transition: background 0.2s; border: none; } .olx-follow:hover { background: #e6eaea; } .olx-original { padding: 8px 15px; background: #002f34; color: white; border-radius: 6px; text-decoration: none; font-size: 14px; display: inline-flex; align-items: center; gap: 8px; font-weight: bold; transition: background 0.2s; } .olx-original:hover { background: #00484e; } /* Location */ .olx-location { font-size: 18px; color: #23e5db; text-decoration: none; display: inline-flex; align-items: center; gap: 6px; } .olx-location:hover { text-decoration: underline; } /* Infinite scroll loader */ .olx-loader { text-align: center; padding: 20px; font-size: 14px; color: #406367; } .olx-spinner { display: inline-block; width: 20px; height: 20px; border: 3px solid rgba(0,0,0,0.1); border-radius: 50%; border-top-color: #23e5db; animation: olx-spin 1s linear infinite; margin-right: 10px; } @keyframes olx-spin { to { transform: rotate(360deg); } } /* Image modal */ .olx-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 9999999; display: flex; flex-direction: column; justify-content: center; align-items: center; } .olx-modal-img { max-width: 90%; max-height: 80vh; object-fit: contain; } .olx-modal-controls { display: flex; gap: 30px; margin-top: 20px; } .olx-modal-btn { background: rgba(255,255,255,0.2); color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 14px; } .olx-modal-btn:hover { background: rgba(255,255,255,0.3); } .olx-modal-close { position: absolute; top: 20px; right: 20px; color: white; background: none; border: none; font-size: 30px; cursor: pointer; } /* Filter controls */ .olx-filter-control { display: flex; align-items: center; gap: 10px; font-size: 14px; color: #406367; cursor: pointer; user-select: none; } .olx-filter-control:hover { color: #002f34; } .olx-switch { position: relative; display: inline-block; width: 36px; height: 20px; } .olx-switch input { opacity: 0; width: 0; height: 0; } .olx-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .3s; border-radius: 20px; } .olx-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 2px; bottom: 2px; background-color: white; transition: .3s; border-radius: 50%; } input:checked + .olx-slider { background-color: #23e5db; } input:checked + .olx-slider:before { transform: translateX(16px); } /* Hide promoted cards if filter is on */ div[data-olx-promoted="true"].olx-hide-promoted { display: none !important; } /* Media query for smaller screens */ @media (max-width: 768px) { .olx-top { flex-direction: column; } .olx-price-section { align-items: flex-start; margin-top: 10px; } .olx-price { font-size: 30px; } .olx-bottom { flex-direction: column; align-items: flex-start; gap: 15px; } .olx-location { font-size: 16px; } } `; document.head.appendChild(style); }; // Create an image modal const setupImageModal = () => { if (document.getElementById('olx-modal')) return; const modal = document.createElement('div'); modal.id = 'olx-modal'; modal.className = 'olx-modal'; modal.style.display = 'none'; modal.innerHTML = ` <button class="olx-modal-close" aria-label="Close">×</button> <img src="" class="olx-modal-img" alt="Product image"> <div class="olx-modal-controls"> <button class="olx-modal-btn olx-prev">< Previous</button> <button class="olx-modal-btn olx-next">Next ></button> </div> `; document.body.appendChild(modal); let currentImages = []; let currentIndex = 0; const img = modal.querySelector('.olx-modal-img'); const prevBtn = modal.querySelector('.olx-prev'); const nextBtn = modal.querySelector('.olx-next'); const closeBtn = modal.querySelector('.olx-modal-close'); // Update image const updateImage = () => { img.src = currentImages[currentIndex]; img.alt = `Image ${currentIndex + 1} of ${currentImages.length}`; }; // Close button closeBtn.addEventListener('click', () => { modal.style.display = 'none'; document.body.style.overflow = ''; }); // Background click modal.addEventListener('click', (e) => { if (e.target === modal) { modal.style.display = 'none'; document.body.style.overflow = ''; } }); // Previous button prevBtn.addEventListener('click', (e) => { e.stopPropagation(); currentIndex = (currentIndex - 1 + currentImages.length) % currentImages.length; updateImage(); }); // Next button nextBtn.addEventListener('click', (e) => { e.stopPropagation(); currentIndex = (currentIndex + 1) % currentImages.length; updateImage(); }); // Keyboard navigation document.addEventListener('keydown', (e) => { if (modal.style.display === 'none') return; if (e.key === 'Escape') { modal.style.display = 'none'; document.body.style.overflow = ''; } else if (e.key === 'ArrowLeft') { prevBtn.click(); } else if (e.key === 'ArrowRight') { nextBtn.click(); } }); // Expose function to show images window.showOlxModal = (images, index = 0) => { currentImages = images; currentIndex = index; updateImage(); modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; }; }; // Extract listing data from a card element const extractCardData = (card) => { const data = { title: '', price: '', url: '', location: '', date: '', condition: '', promoted: false, delivery: false, image: '', images: [] }; // Get URL const link = card.querySelector('a.css-1tqlkj0'); if (link) { data.url = link.getAttribute('href'); } // Get title const title = card.querySelector('h4.css-1g61gc2'); if (title) { data.title = title.textContent.trim(); } // Get price const price = card.querySelector('p[data-testid="ad-price"]'); if (price) { // Get the raw price text const rawPriceText = price.textContent.trim(); // Check for "Безплатно" (Free) and replace with "0 лв." if (rawPriceText.includes('Безплатно')) { data.price = '0 лв.'; } else { // Regular price processing let priceText = rawPriceText; // Clean up any CSS classes that might be in the text priceText = priceText.replace(/\.css-.*$/, ''); // Remove any other non-standard content priceText = priceText.replace(/[^\d\s.,лв€$£¥]+/g, ''); // Remove any extra "в" character that sometimes appears priceText = priceText.replace(' в', ''); data.price = priceText; } } // Get location and date const locationDate = card.querySelector('p[data-testid="location-date"]'); if (locationDate) { const locationDateText = locationDate.textContent.trim(); const parts = locationDateText.split(' - '); if (parts.length > 1) { data.location = parts[0].trim(); data.date = parts[1].trim(); } else { data.location = locationDateText; } } // Get condition const condition = card.querySelector('span.css-iudov9'); if (condition) { data.condition = condition.textContent.trim(); } // Check if promoted const promotedBadge = card.querySelector('div.css-5jyke2 div.css-s3yjnp'); if (promotedBadge) { data.promoted = true; } // Check if delivery is available const deliveryBadge = card.querySelector('div[data-testid="card-delivery-badge"]'); if (deliveryBadge) { data.delivery = true; } // Get main image const img = card.querySelector('img.css-8wsg1m'); if (img) { data.image = img.src; // Try to get higher quality from srcset if (img.srcset) { const srcset = img.srcset; const srcsetParts = srcset.split(','); if (srcsetParts.length > 0) { const bestPart = srcsetParts[srcsetParts.length - 1].trim(); const src = bestPart.split(' ')[0]; if (src) data.image = src; } } // Add to images array data.images.push(data.image); } return data; }; // Extract all images and description from a listing page const extractDetailedData = (html, basicData) => { const data = { ...basicData }; // Create parser const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // Get description const descriptionElement = doc.querySelector('div[data-cy="ad_description"] div'); if (descriptionElement) { data.description = descriptionElement.innerHTML.trim(); } else { data.description = ''; } // Get images from swiper const swiper = doc.querySelector('.swiper-wrapper'); if (swiper) { // Create a temporary array for new images const newImages = []; // Process swiper images first const slides = swiper.querySelectorAll('.swiper-slide'); slides.forEach(slide => { const slideImg = slide.querySelector('img'); if (slideImg) { let imgSrc = slideImg.src; // Try to get higher quality from srcset if (slideImg.srcset) { const srcset = slideImg.srcset; const srcsetParts = srcset.split(','); if (srcsetParts.length > 0) { const bestPart = srcsetParts[srcsetParts.length - 1].trim(); const src = bestPart.split(' ')[0]; if (src) imgSrc = src; } } // Add to images array if not already there and not a placeholder if (imgSrc && !newImages.includes(imgSrc) && imgSrc !== PLACEHOLDER_IMAGE) { newImages.push(imgSrc); } } }); // If we have the main image and it's not a placeholder, add it if not already in the array if (data.image && data.image !== PLACEHOLDER_IMAGE && !newImages.includes(data.image)) { newImages.unshift(data.image); // Add at the beginning } // Now update the data.images with our deduplicated list data.images = newImages; // If we don't have any real images, but we have a placeholder, keep that if (data.images.length === 0 && data.image) { data.images.push(data.image); } } else if (data.image) { // If no swiper but we have a main image, use it data.images = [data.image]; } return data; }; // Create a new card with our premium layout const createPremiumCard = (data) => { const card = document.createElement('div'); card.className = 'olx-card'; // If promoted, add attribute for filtering if (data.promoted) { card.setAttribute('data-olx-promoted', 'true'); // Apply hide class if filter is on const settings = getSettings(); if (settings.hidePromoted) { card.classList.add('olx-hide-promoted'); } } // Determine which image to use as background let backgroundImage = data.image; // If the main image is a placeholder and we have other images, use the first non-placeholder if (backgroundImage === PLACEHOLDER_IMAGE && data.images && data.images.length > 0) { // Find first non-placeholder image for (let i = 0; i < data.images.length; i++) { if (data.images[i] !== PLACEHOLDER_IMAGE) { backgroundImage = data.images[i]; break; } } } // Add background image if (backgroundImage) { card.innerHTML = `<img src="${backgroundImage}" class="olx-bg-image" alt="Background">`; } // Main content container const content = document.createElement('div'); content.className = 'olx-content'; // Top section: Tags, Title, Price, Date let html = '<div class="olx-top">'; // Title section with tags html += '<div class="olx-title-section">'; // Tags row html += '<div class="olx-tags">'; if (data.condition) { html += `<span class="olx-tag olx-condition-tag">${data.condition}</span>`; } if (data.promoted) { html += `<span class="olx-tag olx-promoted-tag">Promoted</span>`; } if (data.delivery) { html += ` <span class="olx-tag olx-delivery-tag"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 24 24"> <path fill="currentColor" fill-rule="evenodd" d="m2 4 1-1h10l2 2h3.5L22 9.667V17l-1 1h-2a3 3 0 1 1-6 0h-3a3 3 0 1 1-6 0H3l-1-1V4Z"/> </svg> Delivery </span> `; } html += '</div>'; // Close tags // Title html += `<div class="olx-title">${data.title || ''}</div>`; html += '</div>'; // Close title section // Price and date section html += '<div class="olx-price-section">'; if (data.date) { html += `<div class="olx-date">${data.date}</div>`; } html += `<div class="olx-price">${data.price || ''}</div>`; html += '</div>'; // Close price section html += '</div>'; // Close top section // Always create the gallery, even with just one image if (data.images && data.images.length > 0) { html += '<div class="olx-gallery">'; // Create an array of images for the gallery that doesn't include the background const galleryImages = data.images.filter(img => img !== backgroundImage); // If we filtered out all images (because they were the same as background), // just show the background image in the gallery too if (galleryImages.length === 0) { html += `<img src="${backgroundImage}" class="olx-thumb" alt="Image 1" data-index="0">`; } else { // Otherwise show all images except the background for (let idx = 0; idx < galleryImages.length; idx++) { // Find the original index in the full images array for the data-index attribute const originalIndex = data.images.indexOf(galleryImages[idx]); html += `<img src="${galleryImages[idx]}" class="olx-thumb" alt="Image ${idx + 1}" data-index="${originalIndex}">`; } } html += '</div>'; } // Description if available if (data.description) { // Estimate if description is short const tempDiv = document.createElement('div'); tempDiv.innerHTML = data.description; const textLength = tempDiv.textContent.length; const isShort = textLength < 100; html += `<div class="olx-desc ${isShort ? 'short' : ''}">${data.description}</div>`; // Only add toggle if not short if (!isShort) { html += '<div class="olx-toggle">+ Show more</div>'; } } // Bottom section with actions and location html += '<div class="olx-bottom">'; // Action buttons html += '<div class="olx-actions">'; html += ` <button class="olx-follow"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"> <path fill="currentColor" fill-rule="evenodd" d="M20.219 10.367 12 20.419 3.806 10.4A3.96 3.96 0 0 1 3 8c0-2.206 1.795-4 4-4a4.004 4.004 0 0 1 3.868 3h2.264A4.003 4.003 0 0 1 17 4c2.206 0 4 1.794 4 4 0 .868-.279 1.698-.781 2.367M17 2a5.999 5.999 0 0 0-5 2.686A5.999 5.999 0 0 0 7 2C3.692 2 1 4.691 1 8a5.97 5.97 0 0 0 1.232 3.633L10.71 22h2.582l8.501-10.399A5.943 5.943 0 0 0 23 8c0-3.309-2.692-6-6-6"></path> </svg> <span>Последвай</span> </button> <a href="${data.url}" class="olx-original" target="_blank"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> <path d="M19 19H5V5h7V3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/> </svg> View Original </a> `; html += '</div>'; // Close actions // Location with Google Maps link if (data.location) { const mapsUrl = `https://www.google.com/maps/search/${encodeURIComponent(data.location)}`; html += ` <a href="${mapsUrl}" class="olx-location" target="_blank"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="currentColor"> <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/> </svg> ${data.location} </a> `; } html += '</div>'; // Close bottom section content.innerHTML = html; card.appendChild(content); // Add event listeners // Thumbnail clicks const thumbs = card.querySelectorAll('.olx-thumb'); thumbs.forEach(thumb => { thumb.addEventListener('click', () => { const index = parseInt(thumb.getAttribute('data-index')); window.showOlxModal(data.images, index); }); }); // Background image click should show gallery const bgImage = card.querySelector('.olx-bg-image'); if (bgImage && data.images && data.images.length > 0) { bgImage.addEventListener('click', () => { // Find the index of the background image in the images array const bgIndex = data.images.indexOf(backgroundImage); window.showOlxModal(data.images, bgIndex >= 0 ? bgIndex : 0); }); } // Toggle description const desc = card.querySelector('.olx-desc:not(.short)'); const toggle = card.querySelector('.olx-toggle'); if (desc && toggle) { toggle.addEventListener('click', () => { if (desc.classList.contains('expanded')) { desc.classList.remove('expanded'); toggle.textContent = '+ Show more'; } else { desc.classList.add('expanded'); toggle.textContent = '- Show less'; } }); } return card; }; // Fetch additional data for a listing and update the card const fetchAndUpdateCard = (card, basicData) => { // Check if we already have this data cached if (dataCache[basicData.url]) { const fullData = dataCache[basicData.url]; replaceCardContent(card, fullData); return; } // Make sure URL is absolute const fullUrl = basicData.url.startsWith('/') ? 'https://www.olx.bg' + basicData.url : basicData.url; // Create temporary content while loading replaceCardContent(card, basicData); // Fetch the listing page GM_xmlhttpRequest({ method: 'GET', url: fullUrl, timeout: 10000, onload: (response) => { if (response.status === 200) { try { // Extract full data const fullData = extractDetailedData(response.responseText, basicData); // Cache the data dataCache[basicData.url] = fullData; // Update card with full data replaceCardContent(card, fullData); } catch (error) { console.error('OLX Detail Error:', error); } } }, onerror: (error) => { console.error('OLX Fetch Error:', error); } }); }; // Replace a card's content with our premium layout const replaceCardContent = (card, data) => { // Create our custom card const premiumCard = createPremiumCard(data); // Replace all content in the original card card.innerHTML = ''; // Copy all children from premium card to original card while (premiumCard.firstChild) { card.appendChild(premiumCard.firstChild); } // Copy relevant attributes from premium card if (premiumCard.hasAttribute('data-olx-promoted')) { card.setAttribute('data-olx-promoted', premiumCard.getAttribute('data-olx-promoted')); } if (premiumCard.classList.contains('olx-hide-promoted')) { card.classList.add('olx-hide-promoted'); } // Copy class names card.className = `css-l9drzq ${premiumCard.className}`; // Mark as processed card.setAttribute('data-olx-processed', 'true'); processedCards.add(card.id || Math.random().toString(36).substring(2, 9)); }; // Filter grid to only show cards with data-testid="l-card" const filterGrid = () => { const grid = document.querySelector('div[data-testid="listing-grid"]'); if (!grid) return; // Get all direct children const children = Array.from(grid.children); // Remove any that don't have data-testid="l-card" children.forEach(child => { if (!child.matches('div[data-testid="l-card"]') && !child.classList.contains('olx-loader')) { child.remove(); } }); }; // Process all listing cards on the page const processCards = () => { const grid = document.querySelector('div[data-testid="listing-grid"]'); if (!grid) return; // Filter grid first filterGrid(); // Find all unprocessed cards const cards = grid.querySelectorAll('div[data-testid="l-card"]:not([data-olx-processed="true"])'); cards.forEach(card => { // Skip if already processed if (processedCards.has(card.id || '')) return; // Extract basic data from the card const basicData = extractCardData(card); // Skip if no URL found if (!basicData.url) return; // Fetch additional data and update the card fetchAndUpdateCard(card, basicData); }); }; // Add promoted filter toggle const addFilterToggle = () => { // Find the pagination control area const filterArea = document.querySelector('div.css-k5itnz'); if (!filterArea) return; // Load settings const settings = getSettings(); // Create toggle element const filterToggle = document.createElement('div'); filterToggle.className = 'olx-filter-control'; filterToggle.innerHTML = ` <label class="olx-switch"> <input type="checkbox" id="olx-hide-promoted" ${settings.hidePromoted ? 'checked' : ''}> <span class="olx-slider"></span> </label> <span>Hide promoted listings</span> `; // Replace the "How are listings ordered?" link filterArea.innerHTML = ''; filterArea.appendChild(filterToggle); // Add event listener for toggle const checkbox = filterToggle.querySelector('#olx-hide-promoted'); checkbox.addEventListener('change', () => { // Update settings settings.hidePromoted = checkbox.checked; saveSettings(settings); // Update visibility of promoted cards const promotedCards = document.querySelectorAll('div[data-olx-promoted="true"]'); promotedCards.forEach(card => { if (checkbox.checked) { card.classList.add('olx-hide-promoted'); } else { card.classList.remove('olx-hide-promoted'); } }); }); }; // Function to detect the current page number and max page from pagination const detectPagination = () => { const pagination = document.querySelector('div[data-testid="pagination-wrapper"]'); if (!pagination) return; // Find active page const activePage = pagination.querySelector('.pagination-item__active'); if (activePage) { const pageLink = activePage.querySelector('a'); if (pageLink) { const pageNum = parseInt(pageLink.textContent); if (!isNaN(pageNum)) { currentPage = pageNum; } } } // Find last page const lastPageLink = pagination.querySelector('li:nth-last-child(2) a'); if (lastPageLink) { const lastNum = parseInt(lastPageLink.textContent); if (!isNaN(lastNum)) { maxPage = lastNum; } } console.log(`OLX Pagination: Current page ${currentPage}, Max page ${maxPage}`); }; // Infinite scroll - load next page const loadNextPage = () => { if (isLoadingMorePages || currentPage >= maxPage) return; // Mark as loading isLoadingMorePages = true; // Create loader element const loader = document.createElement('div'); loader.className = 'olx-loader'; loader.innerHTML = '<div class="olx-spinner"></div> Loading more listings...'; // Add to page const grid = document.querySelector('div[data-testid="listing-grid"]'); if (grid) { grid.appendChild(loader); } // Get next page URL const nextPage = currentPage + 1; // Check if already processed if (processedPages.has(nextPage)) { isLoadingMorePages = false; if (loader.parentNode) { loader.parentNode.removeChild(loader); } return; } // Build URL for next page let nextUrl = window.location.href; if (nextUrl.includes('page=')) { nextUrl = nextUrl.replace(/page=\d+/, `page=${nextPage}`); } else if (nextUrl.includes('?')) { nextUrl += `&page=${nextPage}`; } else { nextUrl += `?page=${nextPage}`; } // Fetch next page GM_xmlhttpRequest({ method: 'GET', url: nextUrl, timeout: 15000, onload: (response) => { // Remove loader if (loader.parentNode) { loader.parentNode.removeChild(loader); } if (response.status === 200) { try { // Parse HTML const parser = new DOMParser(); const doc = parser.parseFromString(response.responseText, 'text/html'); // Get cards from next page const nextPageCards = Array.from(doc.querySelectorAll('div[data-testid="l-card"]')); // Process and add each card to current page nextPageCards.forEach(nextPageCard => { // Extract basic data const basicData = extractCardData(nextPageCard); // Skip if no URL if (!basicData.url) return; // Create a new card element const newCard = document.createElement('div'); newCard.setAttribute('data-testid', 'l-card'); newCard.setAttribute('data-cy', 'l-card'); newCard.className = 'css-l9drzq'; newCard.id = 'card_' + Math.random().toString(36).substring(2, 9); // Add to grid if (grid) { grid.appendChild(newCard); // Fetch details and update fetchAndUpdateCard(newCard, basicData); } }); // Mark page as processed processedPages.add(nextPage); // Update current page currentPage = nextPage; // Reset loading flag isLoadingMorePages = false; } catch (error) { console.error('OLX Next Page Error:', error); isLoadingMorePages = false; } } else { console.error('OLX Next Page Failed:', response.status); isLoadingMorePages = false; } }, onerror: (error) => { console.error('OLX Next Page Request Error:', error); if (loader.parentNode) { loader.parentNode.removeChild(loader); } isLoadingMorePages = false; } }); }; // Check scroll position and load more if needed const checkScroll = () => { if (isLoadingMorePages || currentPage >= maxPage) return; const scrollY = window.scrollY; const visibleHeight = window.innerHeight; const pageHeight = document.documentElement.scrollHeight; const bottomOfPage = scrollY + visibleHeight >= pageHeight - 800; // Load earlier (800px before bottom) if (bottomOfPage) { loadNextPage(); } }; // Setup scroll detection const setupInfiniteScroll = () => { // Detect pagination first detectPagination(); // Check scroll position periodically setInterval(checkScroll, 500); // Also check on scroll with throttling let scrollTimeout; window.addEventListener('scroll', () => { if (scrollTimeout) return; scrollTimeout = setTimeout(() => { checkScroll(); scrollTimeout = null; }, 200); }, { passive: true }); }; // Watch for changes in the DOM to process new cards and pagination const setupObserver = () => { const observer = new MutationObserver((mutations) => { let shouldProcess = false; let checkPagination = false; let checkFilter = false; for (const mutation of mutations) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { // Check for listing cards if (node.matches('div[data-testid="l-card"]') || node.querySelector('div[data-testid="l-card"]')) { shouldProcess = true; } // Check for pagination if (node.matches('div[data-testid="pagination-wrapper"]') || node.querySelector('div[data-testid="pagination-wrapper"]')) { checkPagination = true; } // Check for filter area if (node.matches('div.css-k5itnz') || node.querySelector('div.css-k5itnz')) { checkFilter = true; } } } } if (shouldProcess && checkPagination && checkFilter) break; } if (shouldProcess) { setTimeout(processCards, 100); } if (checkPagination) { setTimeout(detectPagination, 100); } if (checkFilter) { setTimeout(addFilterToggle, 100); } }); observer.observe(document.body, { childList: true, subtree: true }); }; // Initialize script const init = () => { // Add styles addStyles(); // Setup image modal setupImageModal(); // Add filter toggle setTimeout(addFilterToggle, 1000); // Process cards setTimeout(processCards, 1000); // Setup infinite scroll setTimeout(setupInfiniteScroll, 1500); // Setup observer setupObserver(); // Periodic card processing and filtering setInterval(() => { processCards(); filterGrid(); }, 2000); // Handle URL changes (SPA navigation) let lastUrl = location.href; setInterval(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; // Reset page tracking currentPage = 1; maxPage = 1; processedPages.clear(); processedCards.clear(); // Detect pagination and process cards after a delay setTimeout(() => { detectPagination(); addFilterToggle(); processCards(); }, 1000); } }, 1000); }; // Start the script if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();