mirror of
https://github.com/clucraft/PriceGhost.git
synced 2026-07-11 16:22:12 +02:00
Fix stock status false positives for in-stock items
- Remove overly generic pre-order phrases that caused false positives
("available in", "coming in", "arriving in" matched normal text)
- Add in-stock phrase priority check - "in stock", "add to cart",
"add to basket" now take precedence over pre-order detection
- Add Magento 2 stock status detection using stock classes and
add-to-cart buttons
- Bump version to 1.0.2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2c8843ed8a
commit
afa4f0c96a
7 changed files with 89 additions and 26 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -5,6 +5,15 @@ All notable changes to PriceGhost will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.2] - 2026-01-23
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Stock status false positives** - Fixed overly aggressive pre-order detection that incorrectly marked in-stock items as out of stock. Pages with "in stock", "add to cart", or "add to basket" text now correctly prioritize these indicators
|
||||||
|
- **Magento 2 stock detection** - Added proper stock status detection for Magento 2 sites, checking for stock classes and add-to-cart buttons
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.0.1] - 2026-01-23
|
## [1.0.1] - 2026-01-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
@ -105,5 +114,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
| Version | Date | Description |
|
| Version | Date | Description |
|
||||||
|---------|------|-------------|
|
|---------|------|-------------|
|
||||||
|
| 1.0.2 | 2026-01-23 | Fixed stock status false positives for in-stock items |
|
||||||
| 1.0.1 | 2026-01-23 | Bug fixes, JS-rendered price support, pre-order detection |
|
| 1.0.1 | 2026-01-23 | Bug fixes, JS-rendered price support, pre-order detection |
|
||||||
| 1.0.0 | 2026-01-23 | Initial public release |
|
| 1.0.0 | 2026-01-23 | Initial public release |
|
||||||
|
|
|
||||||
4
backend/package-lock.json
generated
4
backend/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "priceghost-backend",
|
"name": "priceghost-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "priceghost-backend",
|
"name": "priceghost-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.24.0",
|
"@anthropic-ai/sdk": "^0.24.0",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "priceghost-backend",
|
"name": "priceghost-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"description": "PriceGhost price tracking API",
|
"description": "PriceGhost price tracking API",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -742,9 +742,36 @@ const siteScrapers: SiteScraper[] = [
|
||||||
$('.fotorama__stage img').first().attr('src') ||
|
$('.fotorama__stage img').first().attr('src') ||
|
||||||
null;
|
null;
|
||||||
|
|
||||||
|
// Stock status detection for Magento 2
|
||||||
|
let stockStatus: StockStatus = 'unknown';
|
||||||
|
|
||||||
|
// Check for Magento's stock status elements
|
||||||
|
const stockElement = $('.product-info-stock-sku .stock').first();
|
||||||
|
const stockText = stockElement.text().toLowerCase();
|
||||||
|
const stockClass = stockElement.attr('class')?.toLowerCase() || '';
|
||||||
|
|
||||||
|
// Magento uses "available" class for in-stock items
|
||||||
|
if (stockClass.includes('available') || stockText.includes('in stock')) {
|
||||||
|
stockStatus = 'in_stock';
|
||||||
|
} else if (stockClass.includes('unavailable') || stockText.includes('out of stock')) {
|
||||||
|
stockStatus = 'out_of_stock';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for add to cart button as backup
|
||||||
|
if (stockStatus === 'unknown') {
|
||||||
|
const addToCartBtn = $('#product-addtocart-button, button.tocart, button[title="Add to Cart"], button[title="Add to Basket"]').length > 0;
|
||||||
|
const outOfStockMsg = $('.out-of-stock, .unavailable, [class*="outofstock"]').length > 0;
|
||||||
|
|
||||||
|
if (addToCartBtn && !outOfStockMsg) {
|
||||||
|
stockStatus = 'in_stock';
|
||||||
|
} else if (outOfStockMsg) {
|
||||||
|
stockStatus = 'out_of_stock';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only return if we found a price (indicates it's likely a Magento site)
|
// Only return if we found a price (indicates it's likely a Magento site)
|
||||||
if (price) {
|
if (price) {
|
||||||
return { name, price, imageUrl };
|
return { name, price, imageUrl, stockStatus };
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
|
@ -1259,13 +1286,11 @@ function extractGenericStockStatus($: CheerioAPI): StockStatus {
|
||||||
|
|
||||||
// Check for pre-order / coming soon indicators BEFORE checking add to cart
|
// Check for pre-order / coming soon indicators BEFORE checking add to cart
|
||||||
// Some sites show a "Pre-order" button that looks like add to cart
|
// Some sites show a "Pre-order" button that looks like add to cart
|
||||||
|
// NOTE: Be careful with generic phrases - "available in" matches "available in stock"!
|
||||||
const preOrderComingSoonPhrases = [
|
const preOrderComingSoonPhrases = [
|
||||||
'coming soon',
|
'coming soon',
|
||||||
'coming in',
|
|
||||||
'available soon',
|
'available soon',
|
||||||
'available in',
|
|
||||||
'arriving soon',
|
'arriving soon',
|
||||||
'arriving in',
|
|
||||||
'releases on',
|
'releases on',
|
||||||
'release date',
|
'release date',
|
||||||
'expected release',
|
'expected release',
|
||||||
|
|
@ -1284,25 +1309,53 @@ function extractGenericStockStatus($: CheerioAPI): StockStatus {
|
||||||
'join waitlist',
|
'join waitlist',
|
||||||
'not yet released',
|
'not yet released',
|
||||||
'not yet available',
|
'not yet available',
|
||||||
'coming this',
|
// Specific future availability phrases (avoid generic "available in" which matches "available in stock")
|
||||||
'coming next',
|
|
||||||
'available starting',
|
'available starting',
|
||||||
|
'available from', // Usually followed by a date
|
||||||
|
'ships in', // Usually indicates future shipping
|
||||||
|
'expected to ship',
|
||||||
|
'estimated arrival',
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const phrase of preOrderComingSoonPhrases) {
|
// Phrases that indicate the product is NOT coming soon (should not trigger out of stock)
|
||||||
if (textToCheck.includes(phrase)) {
|
const inStockPhrases = [
|
||||||
// Double check it's not just a section about pre-orders in general
|
'in stock',
|
||||||
// by looking for the phrase near price/product context
|
'add to cart',
|
||||||
const phraseIndex = textToCheck.indexOf(phrase);
|
'add to basket',
|
||||||
const contextStart = Math.max(0, phraseIndex - 200);
|
'buy now',
|
||||||
const contextEnd = Math.min(textToCheck.length, phraseIndex + 200);
|
'available now',
|
||||||
const context = textToCheck.substring(contextStart, contextEnd);
|
'ships today',
|
||||||
|
'ships immediately',
|
||||||
|
'ready to ship',
|
||||||
|
];
|
||||||
|
|
||||||
// If the context mentions price, buy, cart, or product, it's likely about this product
|
// First, check if the page has strong in-stock indicators
|
||||||
if (context.includes('$') || context.includes('price') ||
|
// If so, don't let pre-order phrase matching override it
|
||||||
context.includes('buy') || context.includes('cart') ||
|
let hasInStockIndicator = false;
|
||||||
context.includes('order') || context.includes('purchase')) {
|
for (const phrase of inStockPhrases) {
|
||||||
return 'out_of_stock';
|
if (textToCheck.includes(phrase)) {
|
||||||
|
hasInStockIndicator = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only check for pre-order/coming soon if we don't have a clear in-stock indicator
|
||||||
|
if (!hasInStockIndicator) {
|
||||||
|
for (const phrase of preOrderComingSoonPhrases) {
|
||||||
|
if (textToCheck.includes(phrase)) {
|
||||||
|
// Double check it's not just a section about pre-orders in general
|
||||||
|
// by looking for the phrase near price/product context
|
||||||
|
const phraseIndex = textToCheck.indexOf(phrase);
|
||||||
|
const contextStart = Math.max(0, phraseIndex - 200);
|
||||||
|
const contextEnd = Math.min(textToCheck.length, phraseIndex + 200);
|
||||||
|
const context = textToCheck.substring(contextStart, contextEnd);
|
||||||
|
|
||||||
|
// If the context mentions price, buy, cart, or product, it's likely about this product
|
||||||
|
if (context.includes('$') || context.includes('price') ||
|
||||||
|
context.includes('buy') || context.includes('cart') ||
|
||||||
|
context.includes('order') || context.includes('purchase')) {
|
||||||
|
return 'out_of_stock';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "priceghost-frontend",
|
"name": "priceghost-frontend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "priceghost-frontend",
|
"name": "priceghost-frontend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "priceghost-frontend",
|
"name": "priceghost-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"releaseDate": "2026-01-23"
|
"releaseDate": "2026-01-23"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue