/** * eBay Content Script * Wird auf eBay-Seiten ausgeführt und extrahiert Verkäufer-/Shop-Daten aus dem DOM */ // Message Listener für Parsing-Anfragen chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.action === "PARSE_EBAY") { try { const parsedData = parseEbayPage(); sendResponse({ ok: true, data: parsedData }); } catch (error) { // Niemals unhandled throws - immer graceful response sendResponse({ ok: true, data: { sellerId: "", shopName: "", market: extractMarketFromHostname(), status: "unknown", stats: {} } }); } return true; // async response } }); /** * Extrahiert Marktplatz aus hostname * @returns {string} Market Code (z.B. "DE", "US", "UK") */ function extractMarketFromHostname() { try { const hostname = window.location.hostname.toLowerCase(); // eBay Domain-Patterns if (hostname.includes('.de') || hostname.includes('ebay.de')) { return 'DE'; } if (hostname.includes('.com') && !hostname.includes('.uk')) { return 'US'; } if (hostname.includes('.uk') || hostname.includes('ebay.co.uk')) { return 'UK'; } if (hostname.includes('.fr') || hostname.includes('ebay.fr')) { return 'FR'; } if (hostname.includes('.it') || hostname.includes('ebay.it')) { return 'IT'; } if (hostname.includes('.es') || hostname.includes('ebay.es')) { return 'ES'; } if (hostname.includes('.nl') || hostname.includes('ebay.nl')) { return 'NL'; } if (hostname.includes('.at') || hostname.includes('ebay.at')) { return 'AT'; } if (hostname.includes('.ch') || hostname.includes('ebay.ch')) { return 'CH'; } // Fallback: erster Teil der Domain nach "ebay." const match = hostname.match(/ebay\.([a-z]{2,3})/); if (match && match[1]) { return match[1].toUpperCase(); } return 'US'; // Default } catch (e) { return 'US'; } } /** * Extrahiert Seller ID aus URL oder DOM * @returns {string} Seller ID */ function extractSellerId() { try { // Methode 1: URL-Patterns const url = window.location.href; const urlLower = url.toLowerCase(); // Pattern: /usr/username oder /str/storename const usrMatch = url.match(/\/usr\/([^\/\?]+)/i); if (usrMatch && usrMatch[1]) { return usrMatch[1].trim(); } const strMatch = url.match(/\/str\/([^\/\?]+)/i); if (strMatch && strMatch[1]) { return strMatch[1].trim(); } // Methode 2: DOM-Elemente suchen // Suche nach verschiedenen Selektoren, die Seller-ID enthalten könnten const possibleSelectors = [ '[data-testid*="seller"]', '.seller-username', '.member-info-username', '[class*="seller-id"]', '[class*="username"]', '[id*="seller"]' ]; for (const selector of possibleSelectors) { try { const element = document.querySelector(selector); if (element) { const text = element.textContent?.trim(); if (text && text.length > 0 && text.length < 100) { return text; } } } catch (e) { // Continue to next selector } } // Methode 3: Meta-Tags try { const metaSeller = document.querySelector('meta[property*="seller"], meta[name*="seller"]'); if (metaSeller && metaSeller.content) { return metaSeller.content.trim(); } } catch (e) { // Continue } return ""; // Nicht gefunden } catch (e) { return ""; } } /** * Extrahiert Shop Name aus DOM * @returns {string} Shop Name */ function extractShopName() { try { // Methode 1: Spezifische Selektoren versuchen const shopNameSelectors = [ 'h1.shop-name', '.store-name', '[data-testid="store-name"]', '.store-title', '[class*="shop-name"]', '[class*="store-title"]', 'h1[class*="store"]', 'h1[class*="shop"]' ]; for (const selector of shopNameSelectors) { try { const element = document.querySelector(selector); if (element) { const text = element.textContent?.trim(); if (text && text.length > 0) { return text; } } } catch (e) { // Continue to next selector } } // Methode 2: document.title parsen try { const title = document.title || ""; // Versuche Muster wie "Shop Name | eBay" zu extrahieren const titleMatch = title.match(/^(.+?)\s*[\|\-]\s*eBay/i); if (titleMatch && titleMatch[1]) { return titleMatch[1].trim(); } // Wenn Titel einfach genug ist, verwende ihn direkt if (title && title.length < 100 && !title.toLowerCase().includes('ebay')) { return title.trim(); } } catch (e) { // Continue } // Methode 3: h1 Tag als Fallback try { const h1 = document.querySelector('h1'); if (h1) { const text = h1.textContent?.trim(); if (text && text.length > 0 && text.length < 200) { return text; } } } catch (e) { // Continue } return ""; // Nicht gefunden } catch (e) { return ""; } } /** * Extrahiert Stats aus DOM (Feedback Score, Positive Rate, etc.) * @returns {object} Stats Objekt */ function extractStats() { const stats = {}; try { const pageText = document.body?.textContent || ""; // Feedback Score try { const feedbackScorePatterns = [ /feedback\s*score[:\s]+(\d+)/i, /(?:score|bewertung)[:\s]+(\d+)/i, /(\d+)\s*feedback/i ]; for (const pattern of feedbackScorePatterns) { const match = pageText.match(pattern); if (match && match[1]) { const score = parseInt(match[1], 10); if (!isNaN(score) && score >= 0) { stats.feedbackScore = score; break; } } } } catch (e) { // Continue } // Positive Rate (%) try { const positiveRatePatterns = [ /positive[:\s]+(\d+\.?\d*)\s*%/i, /(\d+\.?\d*)\s*%\s*positive/i, /(?:rate|quote)[:\s]+(\d+\.?\d*)\s*%/i ]; for (const pattern of positiveRatePatterns) { const match = pageText.match(pattern); if (match && match[1]) { const rate = parseFloat(match[1]); if (!isNaN(rate) && rate >= 0 && rate <= 100) { stats.positiveRate = rate; break; } } } } catch (e) { // Continue } // Feedback Count try { const feedbackCountPatterns = [ /(\d+)\s*(?:bewertungen|ratings|feedbacks?)/i, /(?:count|anzahl)[:\s]+(\d+)/i ]; for (const pattern of feedbackCountPatterns) { const match = pageText.match(pattern); if (match && match[1]) { const count = parseInt(match[1], 10); if (!isNaN(count) && count >= 0) { stats.feedbackCount = count; break; } } } } catch (e) { // Continue } // Items for Sale try { const itemsPatterns = [ /items?\s*(?:for\s*)?sale[:\s]+(\d+)/i, /(\d+)\s*artikel/i, /(\d+)\s*(?:items?|artikel)\s*(?:for\s*)?sale/i ]; for (const pattern of itemsPatterns) { const match = pageText.match(pattern); if (match && match[1]) { const items = parseInt(match[1], 10); if (!isNaN(items) && items >= 0) { stats.itemsForSale = items; break; } } } } catch (e) { // Continue } // Member Since try { const memberSincePatterns = [ /member\s*since[:\s]+(\d{1,2}[\/\-\.]\d{1,2}[\/\-\.]\d{2,4})/i, /mitglied\s*seit[:\s]+(\d{1,2}[\/\-\.]\d{1,2}[\/\-\.]\d{2,4})/i, /(?:since|seit)[:\s]+(\d{4})/i ]; for (const pattern of memberSincePatterns) { const match = pageText.match(pattern); if (match && match[1]) { stats.memberSince = match[1].trim(); break; } } } catch (e) { // Continue } } catch (e) { // Return empty stats object } return stats; } /** * Haupt-Parsing-Funktion * @returns {object} Parsed eBay Account Data */ function parseEbayPage() { try { const sellerId = extractSellerId(); const shopName = extractShopName(); const market = extractMarketFromHostname(); const stats = extractStats(); // Status bestimmen: "active" wenn wir mindestens Shop Name oder Seller ID haben const status = (sellerId || shopName) ? "active" : "unknown"; return { sellerId: sellerId || "", shopName: shopName || "", market: market, status: status, stats: stats }; } catch (error) { // Graceful fallback return { sellerId: "", shopName: "", market: extractMarketFromHostname(), status: "unknown", stats: {} }; } }