good-eggs-mcp-server 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/shared/server.js +95 -48
package/package.json
CHANGED
package/shared/server.js
CHANGED
|
@@ -389,19 +389,32 @@ export class GoodEggsClient {
|
|
|
389
389
|
// Extract past order information
|
|
390
390
|
const orders = await page.evaluate(() => {
|
|
391
391
|
const orderList = [];
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
const countEl = el.querySelector('[class*="count"], [class*="items"]');
|
|
392
|
+
// Good Eggs reorder page uses 'reorder-page__grid__header' class for order date headers
|
|
393
|
+
// Each header contains a <p> element with text like "Delivered Saturday, January 10th"
|
|
394
|
+
const headerElements = document.querySelectorAll('.reorder-page__grid__header');
|
|
395
|
+
headerElements.forEach((header) => {
|
|
396
|
+
const dateEl = header.querySelector('p');
|
|
398
397
|
const dateText = dateEl?.textContent?.trim();
|
|
399
398
|
if (!dateText)
|
|
400
399
|
return;
|
|
400
|
+
// Skip non-order headers like "Based on your shopping" recommendations section
|
|
401
|
+
if (!dateText.includes('Delivered'))
|
|
402
|
+
return;
|
|
403
|
+
// Extract just the date part (e.g., "Saturday, January 10th" from "Delivered Saturday, January 10th")
|
|
404
|
+
const dateMatch = dateText.match(/Delivered\s+(.+)/);
|
|
405
|
+
const cleanDate = dateMatch ? dateMatch[1] : dateText;
|
|
406
|
+
// Count the products in this order section
|
|
407
|
+
// The products follow the header in the DOM as sibling elements with js-product-link
|
|
408
|
+
let itemCount = 0;
|
|
409
|
+
let sibling = header.nextElementSibling;
|
|
410
|
+
while (sibling && !sibling.classList.contains('reorder-page__grid__header')) {
|
|
411
|
+
const productLinks = sibling.querySelectorAll('a.js-product-link');
|
|
412
|
+
itemCount += productLinks.length;
|
|
413
|
+
sibling = sibling.nextElementSibling;
|
|
414
|
+
}
|
|
401
415
|
orderList.push({
|
|
402
|
-
date:
|
|
403
|
-
|
|
404
|
-
itemCount: countEl?.textContent ? parseInt(countEl.textContent) : undefined,
|
|
416
|
+
date: cleanDate,
|
|
417
|
+
itemCount: itemCount > 0 ? itemCount : undefined,
|
|
405
418
|
});
|
|
406
419
|
});
|
|
407
420
|
return orderList;
|
|
@@ -420,52 +433,86 @@ export class GoodEggsClient {
|
|
|
420
433
|
if (page.url().includes('/signin')) {
|
|
421
434
|
throw new Error('Not logged in. Cannot access past orders.');
|
|
422
435
|
}
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
await orderLink.click();
|
|
427
|
-
await page.waitForTimeout(2000);
|
|
428
|
-
}
|
|
429
|
-
// Extract items from the order
|
|
430
|
-
const items = await page.evaluate(() => {
|
|
436
|
+
// Extract items from the specific order date section
|
|
437
|
+
// The reorder page displays all orders with their products inline (not as separate pages)
|
|
438
|
+
const items = await page.evaluate((targetDate) => {
|
|
431
439
|
const products = [];
|
|
432
440
|
const seen = new Set();
|
|
433
|
-
//
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
441
|
+
// Find all order headers
|
|
442
|
+
const headerElements = Array.from(document.querySelectorAll('.reorder-page__grid__header'));
|
|
443
|
+
// Find the header that matches our target date
|
|
444
|
+
let targetHeader = null;
|
|
445
|
+
for (const header of headerElements) {
|
|
446
|
+
const dateEl = header.querySelector('p');
|
|
447
|
+
const dateText = dateEl?.textContent?.trim() || '';
|
|
448
|
+
// Check if this header matches (could be exact match or partial match)
|
|
449
|
+
if (dateText.includes(targetDate) ||
|
|
450
|
+
targetDate.includes(dateText.replace('Delivered ', ''))) {
|
|
451
|
+
targetHeader = header;
|
|
452
|
+
break;
|
|
440
453
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
454
|
+
}
|
|
455
|
+
if (!targetHeader) {
|
|
456
|
+
return products;
|
|
457
|
+
}
|
|
458
|
+
// Get the parent container that holds all the products for this order
|
|
459
|
+
// Products are siblings of the header within the same grid section
|
|
460
|
+
const parent = targetHeader.parentElement;
|
|
461
|
+
if (!parent) {
|
|
462
|
+
return products;
|
|
463
|
+
}
|
|
464
|
+
// Find all product links within this section (between this header and the next one)
|
|
465
|
+
let foundHeader = false;
|
|
466
|
+
const children = Array.from(parent.children);
|
|
467
|
+
for (const child of children) {
|
|
468
|
+
if (child === targetHeader) {
|
|
469
|
+
foundHeader = true;
|
|
470
|
+
continue;
|
|
446
471
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const priceEl = container.querySelector('[class*="price"]');
|
|
451
|
-
const imgEl = container.querySelector('img');
|
|
452
|
-
let name = nameEl?.textContent?.trim();
|
|
453
|
-
if (!name || name.length < 3) {
|
|
454
|
-
name = link.textContent?.trim();
|
|
472
|
+
// Stop when we hit the next header
|
|
473
|
+
if (foundHeader && child.classList.contains('reorder-page__grid__header')) {
|
|
474
|
+
break;
|
|
455
475
|
}
|
|
456
|
-
if (!
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
476
|
+
if (!foundHeader) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
// Extract product links from this child element
|
|
480
|
+
const productLinks = child.querySelectorAll('a.js-product-link');
|
|
481
|
+
productLinks.forEach((el) => {
|
|
482
|
+
const link = el;
|
|
483
|
+
const href = link.href;
|
|
484
|
+
if (!href || seen.has(href) || href.includes('/reorder') || href.includes('/signin')) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
// Validate URL structure
|
|
488
|
+
const urlPath = new URL(href).pathname;
|
|
489
|
+
const segments = urlPath.split('/').filter((s) => s.length > 0);
|
|
490
|
+
if (segments.length < 3) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const container = link.closest('div[class*="product"], article, [class*="card"]') || link;
|
|
494
|
+
const nameEl = container.querySelector('h2, h3, [class*="title"], [class*="name"]');
|
|
495
|
+
const brandEl = container.querySelector('[class*="brand"], [class*="producer"]');
|
|
496
|
+
const priceEl = container.querySelector('[class*="price"]');
|
|
497
|
+
const imgEl = container.querySelector('img');
|
|
498
|
+
let name = nameEl?.textContent?.trim();
|
|
499
|
+
if (!name || name.length < 3) {
|
|
500
|
+
name = link.textContent?.trim();
|
|
501
|
+
}
|
|
502
|
+
if (!name || name.length < 3)
|
|
503
|
+
return;
|
|
504
|
+
seen.add(href);
|
|
505
|
+
products.push({
|
|
506
|
+
url: href,
|
|
507
|
+
name: name,
|
|
508
|
+
brand: brandEl?.textContent?.trim() || '',
|
|
509
|
+
price: priceEl?.textContent?.trim() || '',
|
|
510
|
+
imageUrl: imgEl?.src || undefined,
|
|
511
|
+
});
|
|
465
512
|
});
|
|
466
|
-
}
|
|
513
|
+
}
|
|
467
514
|
return products;
|
|
468
|
-
});
|
|
515
|
+
}, orderDate);
|
|
469
516
|
return items;
|
|
470
517
|
}
|
|
471
518
|
async addFavorite(groceryUrl) {
|