Merge remote changes and update project files
This commit is contained in:
@@ -6,29 +6,15 @@
|
||||
// Log dass Content Script geladen wurde
|
||||
console.log("[EBAY-CONTENT] Content script loaded on:", window.location.href);
|
||||
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebay-content-script.js:7',message:'ebay-content-script: loaded',data:{url:window.location.href,readyState:document.readyState},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{});
|
||||
// #endregion
|
||||
|
||||
// Message Listener für Parsing-Anfragen
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebay-content-script.js:10',message:'ebay-content-script: message received',data:{action:message.action},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
||||
// #endregion
|
||||
|
||||
// Ping-Handler für Content Script Verfügbarkeitsprüfung
|
||||
if (message.action === "PING") {
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebay-content-script.js:13',message:'ebay-content-script: PING handler',data:{},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
||||
// #endregion
|
||||
sendResponse({ ok: true, ready: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.action === "PARSE_EBAY") {
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebay-content-script.js:17',message:'ebay-content-script: PARSE_EBAY handler',data:{readyState:document.readyState},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
||||
// #endregion
|
||||
// Wrapper um sicherzustellen, dass immer eine Antwort gesendet wird
|
||||
try {
|
||||
// Prüfe ob DOM bereit ist
|
||||
@@ -75,6 +61,17 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
return true; // async response
|
||||
}
|
||||
|
||||
if (message.action === "PARSE_ITEM_DETAIL") {
|
||||
try {
|
||||
const detail = parseItemDetailPage();
|
||||
sendResponse({ ok: true, data: detail });
|
||||
} catch (e) {
|
||||
console.warn("[EBAY-CONTENT] PARSE_ITEM_DETAIL error:", e);
|
||||
sendResponse({ ok: true, data: {} });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.action === "PARSE_PRODUCT_LIST") {
|
||||
// async function, need to return promise
|
||||
parseProductList()
|
||||
@@ -183,6 +180,12 @@ function extractSellerId() {
|
||||
return strMatch[1].trim();
|
||||
}
|
||||
|
||||
// Pattern: _ssn=username (Seller Name Parameter in Suchergebnis-URLs)
|
||||
const ssnMatch = url.match(/[?&]_ssn=([^&]+)/i);
|
||||
if (ssnMatch && ssnMatch[1]) {
|
||||
return decodeURIComponent(ssnMatch[1]).trim();
|
||||
}
|
||||
|
||||
// Methode 2: DOM-Elemente suchen
|
||||
// Suche nach verschiedenen Selektoren, die Seller-ID enthalten könnten
|
||||
const possibleSelectors = [
|
||||
@@ -683,7 +686,6 @@ async function parseProductList() {
|
||||
let pageType = detectPageType();
|
||||
let finalUrl = window.location.href;
|
||||
let reason = null;
|
||||
|
||||
try {
|
||||
// Schritt 1: Warte auf Item-Links (Polling)
|
||||
let links = await waitForItemLinks(4000, 250, 5);
|
||||
@@ -768,17 +770,14 @@ async function parseProductList() {
|
||||
}
|
||||
}
|
||||
|
||||
// Return max 60 items (first page)
|
||||
const result = parsedItems.slice(0, 60);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
items: result,
|
||||
items: parsedItems,
|
||||
meta: {
|
||||
pageType,
|
||||
finalUrl,
|
||||
attempts,
|
||||
reason: reason || (result.length > 0 ? "items_found" : "parsed_zero_items")
|
||||
reason: reason || (parsedItems.length > 0 ? "items_found" : "parsed_zero_items")
|
||||
}
|
||||
};
|
||||
|
||||
@@ -796,66 +795,103 @@ async function parseProductList() {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parst Produktdetail-Seite (/itm/...) für title, price, currency, category, condition,
|
||||
* quantityAvailable, quantitySold, watchCount, inCartsCount.
|
||||
*/
|
||||
function parseItemDetailPage() {
|
||||
const out = {
|
||||
title: "",
|
||||
price: null,
|
||||
currency: null,
|
||||
category: null,
|
||||
condition: null,
|
||||
quantityAvailable: null,
|
||||
quantitySold: null,
|
||||
watchCount: null,
|
||||
inCartsCount: null
|
||||
};
|
||||
try {
|
||||
const url = window.location.href;
|
||||
const urlLower = url.toLowerCase();
|
||||
|
||||
// Determine page type
|
||||
const isStorePage = urlLower.includes('/str/') || urlLower.includes('/store/');
|
||||
const isSellerPage = urlLower.includes('/usr/');
|
||||
|
||||
// Check if seller profile without items (try to find link to listings)
|
||||
if (isSellerPage && !isStorePage) {
|
||||
const itemsLink = document.querySelector('a[href*="/usr/"][href*="?items="]') ||
|
||||
document.querySelector('a[href*="schid=mksr"]') ||
|
||||
Array.from(document.querySelectorAll('a')).find(a => {
|
||||
const text = (a.textContent || '').toLowerCase();
|
||||
return text.includes('artikel') || text.includes('angebote') ||
|
||||
text.includes('items for sale') || text.includes('see all items');
|
||||
});
|
||||
|
||||
if (!itemsLink) {
|
||||
// Try to find item cards directly
|
||||
const hasItems = findItemLinks().length > 0;
|
||||
if (!hasItems) {
|
||||
throw new Error("no_items_page");
|
||||
const titleEl = document.querySelector("[data-testid=\"x-item-title\"] h1 .ux-textspans") ||
|
||||
document.querySelector("h1.x-item-title__mainTitle .ux-textspans");
|
||||
if (titleEl) {
|
||||
out.title = (titleEl.textContent || "").trim().replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
const priceEl = document.querySelector("[data-testid=\"x-price-primary\"] .ux-textspans");
|
||||
if (priceEl) {
|
||||
const priceText = (priceEl.textContent || "").trim();
|
||||
const parsed = parsePrice(priceText);
|
||||
out.price = parsed.price;
|
||||
out.currency = parsed.currency;
|
||||
}
|
||||
|
||||
const breadcrumbs = document.querySelectorAll("a.seo-breadcrumb-text span");
|
||||
if (breadcrumbs.length > 0) {
|
||||
const parts = [];
|
||||
breadcrumbs.forEach((span) => {
|
||||
const t = (span.textContent || "").trim();
|
||||
if (t) parts.push(t);
|
||||
});
|
||||
out.category = parts.join(" > ");
|
||||
}
|
||||
|
||||
const condEl = document.querySelector("dd.ux-labels-values__values .ux-textspans");
|
||||
if (condEl) {
|
||||
const raw = (condEl.textContent || "").trim();
|
||||
const colonIdx = raw.indexOf(":");
|
||||
out.condition = colonIdx >= 0 ? raw.slice(0, colonIdx).trim() : raw.slice(0, 80);
|
||||
}
|
||||
|
||||
// x-quantity__availability: "9 verfügbar", "8 verkauft"
|
||||
const availEl = document.querySelector(".x-quantity__availability");
|
||||
if (availEl) {
|
||||
const text = (availEl.textContent || "").trim();
|
||||
const verfMatch = text.match(/(\d+)\s*verfügbar/i);
|
||||
if (verfMatch && verfMatch[1]) {
|
||||
const n = parseInt(verfMatch[1], 10);
|
||||
if (!isNaN(n) && n >= 0) out.quantityAvailable = n;
|
||||
}
|
||||
const verkMatch = text.match(/(\d+)\s*verkauft/i);
|
||||
if (verkMatch && verkMatch[1]) {
|
||||
const n = parseInt(verkMatch[1], 10);
|
||||
if (!isNaN(n) && n >= 0) out.quantitySold = n;
|
||||
}
|
||||
}
|
||||
|
||||
// x-watch-heart-btn: .x-watch-heart-btn-text "14" oder aria-label "14 Beobachter"
|
||||
const watchBtn = document.querySelector("button.x-watch-heart-btn");
|
||||
if (watchBtn) {
|
||||
const textEl = watchBtn.querySelector(".x-watch-heart-btn-text");
|
||||
if (textEl) {
|
||||
const n = parseInt((textEl.textContent || "").trim().replace(/\D/g, ""), 10);
|
||||
if (!isNaN(n) && n >= 0) out.watchCount = n;
|
||||
}
|
||||
if (out.watchCount == null) {
|
||||
const label = (watchBtn.getAttribute("aria-label") || "").trim();
|
||||
const m = label.match(/(\d+)\s*Beobachter/i);
|
||||
if (m && m[1]) {
|
||||
const n = parseInt(m[1], 10);
|
||||
if (!isNaN(n) && n >= 0) out.watchCount = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract items
|
||||
const items = findItemLinks();
|
||||
|
||||
if (items.length === 0) {
|
||||
throw new Error("no_items_found");
|
||||
}
|
||||
|
||||
// Parse each item
|
||||
const parsedItems = [];
|
||||
const seenIds = new Set();
|
||||
|
||||
for (const itemLink of items) {
|
||||
try {
|
||||
const item = parseItemFromLink(itemLink);
|
||||
|
||||
// Deduplicate by platformProductId
|
||||
if (item.platformProductId && !seenIds.has(item.platformProductId)) {
|
||||
seenIds.add(item.platformProductId);
|
||||
parsedItems.push(item);
|
||||
}
|
||||
} catch (e) {
|
||||
// Continue with next item if one fails
|
||||
console.warn("Failed to parse item:", e);
|
||||
|
||||
// [data-testid="x-ebay-signal"]: "In 8 Warenkörben"
|
||||
const signalEl = document.querySelector("[data-testid=\"x-ebay-signal\"]");
|
||||
if (signalEl) {
|
||||
const text = (signalEl.textContent || "").trim();
|
||||
const m = text.match(/in\s*(\d+)\s*warenkörben/i);
|
||||
if (m && m[1]) {
|
||||
const n = parseInt(m[1], 10);
|
||||
if (!isNaN(n) && n >= 0) out.inCartsCount = n;
|
||||
}
|
||||
}
|
||||
|
||||
// Return max 60 items (first page)
|
||||
return parsedItems.slice(0, 60);
|
||||
|
||||
} catch (error) {
|
||||
// Re-throw to be caught by message handler
|
||||
throw error;
|
||||
} catch (e) {
|
||||
console.warn("[EBAY-CONTENT] parseItemDetailPage error:", e);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user