361 lines
9.0 KiB
JavaScript
361 lines
9.0 KiB
JavaScript
/**
|
|
* 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: {}
|
|
};
|
|
}
|
|
} |