Files
eship/Extension/ebay-content-script.js

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: {}
};
}
}