Update Extension files and add deploy script
This commit is contained in:
@@ -26,17 +26,39 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
}
|
||||
|
||||
if (message.action === "PARSE_PRODUCT_LIST") {
|
||||
try {
|
||||
const items = parseProductList();
|
||||
sendResponse({ ok: true, data: { items } });
|
||||
} catch (error) {
|
||||
// Niemals unhandled throws - immer graceful response
|
||||
console.error("Error parsing product list:", error);
|
||||
sendResponse({
|
||||
ok: false,
|
||||
error: error.message || "Failed to parse product list"
|
||||
// async function, need to return promise
|
||||
parseProductList()
|
||||
.then(result => {
|
||||
// result hat bereits die Struktur { ok, items?, error?, meta }
|
||||
if (result.ok) {
|
||||
sendResponse({
|
||||
ok: true,
|
||||
items: result.items || [],
|
||||
meta: result.meta || {}
|
||||
});
|
||||
} else {
|
||||
sendResponse({
|
||||
ok: false,
|
||||
error: result.error || "Failed to parse product list",
|
||||
meta: result.meta || {}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Niemals unhandled throws - immer graceful response
|
||||
console.error("Error parsing product list:", error);
|
||||
const pageType = detectPageType();
|
||||
sendResponse({
|
||||
ok: false,
|
||||
error: error.message || "Failed to parse product list",
|
||||
meta: {
|
||||
pageType,
|
||||
finalUrl: window.location.href,
|
||||
attempts: 1,
|
||||
reason: "unhandled_error"
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return true; // async response
|
||||
}
|
||||
});
|
||||
@@ -471,10 +493,259 @@ function parseEbayPage() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parst Produktliste von eBay Storefront oder Seller Listings
|
||||
* @returns {Array} Array von Produkt-Items
|
||||
* Erkennt den Seitentyp basierend auf URL und DOM
|
||||
* @returns {string} Page Type: "storefront" | "seller_profile" | "feedback" | "search_results" | "unknown"
|
||||
*/
|
||||
function parseProductList() {
|
||||
function detectPageType() {
|
||||
try {
|
||||
const pathname = window.location.pathname.toLowerCase();
|
||||
const search = window.location.search.toLowerCase();
|
||||
|
||||
// Storefront
|
||||
if (pathname.includes("/str/")) {
|
||||
return "storefront";
|
||||
}
|
||||
|
||||
// Seller Profile
|
||||
if (pathname.includes("/usr/")) {
|
||||
return "seller_profile";
|
||||
}
|
||||
|
||||
// Feedback
|
||||
if (search.includes("_tab=feedback") || pathname.includes("feedback")) {
|
||||
return "feedback";
|
||||
}
|
||||
|
||||
// Search Results
|
||||
if (document.querySelector("ul.srp-results")) {
|
||||
return "search_results";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
} catch (e) {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wartet auf Item-Links, bis minLinks gefunden wurden oder Timeout
|
||||
* @param {number} maxMs - Maximale Wartezeit in ms (default: 4000)
|
||||
* @param {number} intervalMs - Intervall zwischen Checks in ms (default: 250)
|
||||
* @param {number} minLinks - Minimale Anzahl Links (default: 5)
|
||||
* @returns {Promise<Array>} Array von Link-Elementen (kann 0 sein)
|
||||
*/
|
||||
async function waitForItemLinks(maxMs = 4000, intervalMs = 250, minLinks = 5) {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < maxMs) {
|
||||
const links = findItemLinks();
|
||||
if (links.length >= minLinks) {
|
||||
return links;
|
||||
}
|
||||
|
||||
// Warte auf nächsten Check
|
||||
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
||||
}
|
||||
|
||||
// Timeout: gib gefundene Links zurück (kann 0 sein)
|
||||
return findItemLinks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Findet Navigations-Link zu Items-Seite (Angebote/Artikel)
|
||||
* @returns {string|null} Absolute URL oder null
|
||||
*/
|
||||
function findItemsNavigationLink() {
|
||||
try {
|
||||
const allLinks = Array.from(document.querySelectorAll("a"));
|
||||
const candidates = [];
|
||||
|
||||
for (const link of allLinks) {
|
||||
const href = link.href || link.getAttribute("href");
|
||||
const text = (link.textContent || "").toLowerCase().trim();
|
||||
|
||||
if (!href) continue;
|
||||
|
||||
const hrefLower = href.toLowerCase();
|
||||
|
||||
// Prüfe Anchor-Text
|
||||
const textMatches =
|
||||
text.includes("artikel") ||
|
||||
text.includes("angebote") ||
|
||||
text.includes("items") ||
|
||||
text.includes("items for sale") ||
|
||||
text.includes("shop") ||
|
||||
text.includes("shop anzeigen");
|
||||
|
||||
// Prüfe href-Patterns
|
||||
const hrefMatches =
|
||||
hrefLower.includes("/sch/") ||
|
||||
hrefLower.includes("/s/i.html") ||
|
||||
hrefLower.includes("/str/") ||
|
||||
hrefLower.includes("?_tab=selling");
|
||||
|
||||
if (textMatches || hrefMatches) {
|
||||
// Berechne Priorität
|
||||
let priority = 0;
|
||||
if (hrefLower.includes("/sch/")) priority = 1;
|
||||
else if (hrefLower.includes("/str/")) priority = 2;
|
||||
else if (hrefLower.includes("/s/i.html")) priority = 3;
|
||||
else if (text.includes("items")) priority = 4;
|
||||
else priority = 5;
|
||||
|
||||
candidates.push({ link, href, priority });
|
||||
}
|
||||
}
|
||||
|
||||
if (candidates.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sortiere nach Priorität (niedrigste Zahl = höchste Priorität)
|
||||
candidates.sort((a, b) => a.priority - b.priority);
|
||||
|
||||
const bestCandidate = candidates[0].href;
|
||||
const currentUrl = window.location.href;
|
||||
|
||||
// Prüfe ob URL sich unterscheidet
|
||||
if (bestCandidate === currentUrl) {
|
||||
return null; // Gleiche URL, keine Navigation nötig
|
||||
}
|
||||
|
||||
// Erstelle absolute URL falls nötig
|
||||
try {
|
||||
const url = new URL(bestCandidate, window.location.origin);
|
||||
return url.href;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parst Produktliste von eBay Storefront oder Seller Listings
|
||||
* @returns {Promise<{ok: boolean, items?: Array, error?: string, meta: object}>}
|
||||
*/
|
||||
async function parseProductList() {
|
||||
let attempts = 1;
|
||||
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);
|
||||
|
||||
// Schritt 2: Wenn keine Links gefunden, versuche Auto-Navigation (max. 1 Retry)
|
||||
if (links.length === 0 && attempts === 1) {
|
||||
const itemsLink = findItemsNavigationLink();
|
||||
|
||||
if (itemsLink && itemsLink !== window.location.href) {
|
||||
// Navigiere zur Items-Seite
|
||||
window.location.href = itemsLink;
|
||||
|
||||
// Warte auf DOM-Ready nach Navigation
|
||||
await new Promise(resolve => setTimeout(resolve, 1200));
|
||||
|
||||
// Warte erneut auf Item-Links (längeres Timeout nach Navigation)
|
||||
links = await waitForItemLinks(5000, 250, 5);
|
||||
|
||||
attempts = 2;
|
||||
pageType = detectPageType();
|
||||
finalUrl = window.location.href;
|
||||
reason = "navigated_to_items_page";
|
||||
|
||||
// Wenn immer noch keine Links, gib Fehler zurück
|
||||
if (links.length === 0) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "no_items_found",
|
||||
meta: {
|
||||
pageType,
|
||||
finalUrl,
|
||||
attempts,
|
||||
reason: "no_items_found_after_navigation"
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Kein Navigations-Link gefunden
|
||||
return {
|
||||
ok: false,
|
||||
error: "no_items_found",
|
||||
meta: {
|
||||
pageType,
|
||||
finalUrl,
|
||||
attempts,
|
||||
reason: links.length === 0 ? "no_items_links_on_page" : "timed_out_waiting_for_items"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Schritt 3: Parse Items
|
||||
if (links.length === 0) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "no_items_found",
|
||||
meta: {
|
||||
pageType,
|
||||
finalUrl,
|
||||
attempts,
|
||||
reason: reason || "no_items_links_on_page"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Parse each item
|
||||
const parsedItems = [];
|
||||
const seenIds = new Set();
|
||||
|
||||
for (const itemLink of links) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Return max 60 items (first page)
|
||||
const result = parsedItems.slice(0, 60);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
items: result,
|
||||
meta: {
|
||||
pageType,
|
||||
finalUrl,
|
||||
attempts,
|
||||
reason: reason || (result.length > 0 ? "items_found" : "parsed_zero_items")
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
// Graceful error handling
|
||||
return {
|
||||
ok: false,
|
||||
error: error.message || "Failed to parse product list",
|
||||
meta: {
|
||||
pageType,
|
||||
finalUrl,
|
||||
attempts,
|
||||
reason: "parse_error"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
try {
|
||||
const url = window.location.href;
|
||||
const urlLower = url.toLowerCase();
|
||||
|
||||
Reference in New Issue
Block a user