mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-17 12:18:17 +02:00
Merge branch 'origin/antigravity' into antigravity
Resolved conflict in browserless_service.py by merging Amazon interstitial selectors. Combined local robust selectors with remote additions for maximum coverage.
This commit is contained in:
commit
86aae8f628
6 files changed
+94
-12
No files matched your search
@@ -53,8 +53,12 @@ POPUP_SELECTORS = [
|
||||
|
||||
# Amazon Interstitials (Soft blocks)
|
||||
"button:has-text('Continuer les achats')",
|
||||
"span:has-text('Continuer les achats')",
|
||||
"a:has-text('Continuer les achats')",
|
||||
"input[value='Continuer les achats']",
|
||||
"input[value='Continue shopping']",
|
||||
"span.a-button-inner > input.a-button-input[type='submit']",
|
||||
"form:has-text('Continuer les achats') input[type='submit']",
|
||||
"[aria-labelledby='continue-shopping-label']",
|
||||
]
|
||||
|
||||
|
||||
@@ -127,6 +127,18 @@ async def process_item_check(item_id: int):
|
||||
if not screenshot_path:
|
||||
raise Exception("Failed to capture screenshot")
|
||||
|
||||
# Update the main item screenshot for the UI
|
||||
try:
|
||||
import shutil
|
||||
import os
|
||||
dest_path = f"/app/screenshots/item_{item_id}.png"
|
||||
# Ensure directory exists
|
||||
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
||||
shutil.copy2(screenshot_path, dest_path)
|
||||
logger.info(f"Updated main screenshot for item {item_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update main screenshot: {e}")
|
||||
|
||||
if not (ai_result := await AIService.analyze_image(screenshot_path, page_text=page_text)):
|
||||
raise Exception("AI analysis failed")
|
||||
|
||||
|
||||
@@ -422,6 +422,10 @@ def seed_default_sites(db: Session) -> int:
|
||||
site = existing_sites[clean_domain]
|
||||
changed = False
|
||||
for key, value in site_data.items():
|
||||
# Skip updating user-configurable fields to preserve preferences
|
||||
if key in ["is_active", "priority"]:
|
||||
continue
|
||||
|
||||
if getattr(site, key) != value:
|
||||
setattr(site, key, value)
|
||||
changed = True
|
||||
|
||||
@@ -245,17 +245,17 @@ export function ItemCard({ item, onEdit, onDelete, onCheck, onZoom, onCategoryUp
|
||||
|
||||
<div className="flex gap-1">
|
||||
{onViewHistory && (
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => onViewHistory(item)} title="Historique des prix">
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => onViewHistory(item)} title={t('dashboard.history') || "Historique"}>
|
||||
<History className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => onEdit(item)}>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => onEdit(item)} title={t('common.edit') || "Modifier"}>
|
||||
<Edit2 className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => onCheck(item.id)} disabled={item.is_refreshing}>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => onCheck(item.id)} disabled={item.is_refreshing} title={t('dashboard.refresh') || "Forcer la mise à jour"}>
|
||||
<RefreshCw className={cn("h-3 w-3", item.is_refreshing && "animate-spin text-primary")} />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6 text-destructive hover:text-destructive" onClick={() => onDelete(item)}>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6 text-destructive hover:text-destructive" onClick={() => onDelete(item)} title={t('common.delete') || "Supprimer"}>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -138,7 +138,7 @@ export function ItemModal({ item, onClose, onSaved, open, categories = [] }) {
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="custom_category"
|
||||
placeholder="Enter new category"
|
||||
placeholder="Entrer une nouvelle catégorie"
|
||||
value={formData.category}
|
||||
onChange={e => setFormData({ ...formData, category: e.target.value })}
|
||||
autoFocus
|
||||
@@ -151,7 +151,7 @@ export function ItemModal({ item, onClose, onSaved, open, categories = [] }) {
|
||||
setShowCustomInput(false);
|
||||
setFormData({ ...formData, category: '' });
|
||||
}}
|
||||
title="Select existing category"
|
||||
title="Sélectionner une catégorie existante"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -171,30 +171,30 @@ export function ItemModal({ item, onClose, onSaved, open, categories = [] }) {
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select a category" />
|
||||
<SelectValue placeholder="Sélectionner une catégorie" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">None</SelectItem>
|
||||
<SelectItem value="none">Aucune</SelectItem>
|
||||
{categories.map(cat => (
|
||||
<SelectItem key={cat} value={cat}>{cat}</SelectItem>
|
||||
))}
|
||||
<SelectItem value="__custom__">Custom...</SelectItem>
|
||||
<SelectItem value="__custom__">Autre...</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="channel">Notification Channel</Label>
|
||||
<Label htmlFor="channel">Canal de Notification</Label>
|
||||
<Select
|
||||
value={formData.notification_channel_id}
|
||||
onValueChange={(val) => setFormData({ ...formData, notification_channel_id: val })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a channel" />
|
||||
<SelectValue placeholder="Sélectionner un canal" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">None</SelectItem>
|
||||
<SelectItem value="none">Aucun</SelectItem>
|
||||
{channels.map(channel => (
|
||||
<SelectItem key={channel.id} value={channel.id.toString()}>
|
||||
{channel.name} ({channel.type})
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import asyncio
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
async def main():
|
||||
async with async_playwright() as p:
|
||||
# Launch with headless=True to mimic server environment
|
||||
browser = await p.chromium.launch(headless=True)
|
||||
context = await browser.new_context(
|
||||
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||
)
|
||||
page = await context.new_page()
|
||||
|
||||
# Try a product URL that might trigger the check
|
||||
url = "https://www.amazon.fr/dp/B07S58MPKW"
|
||||
print(f"Navigating to {url}")
|
||||
|
||||
try:
|
||||
await page.goto(url)
|
||||
await page.wait_for_timeout(5000)
|
||||
|
||||
content = await page.content()
|
||||
|
||||
if "Continuer les achats" in content:
|
||||
print("🚨 Popup detected!")
|
||||
with open("amazon_popup.html", "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
await page.screenshot(path="amazon_popup.png")
|
||||
|
||||
# Analyze the button
|
||||
print("Searching for button...")
|
||||
|
||||
# Try various locators
|
||||
locators = [
|
||||
"button",
|
||||
"input[type='submit']",
|
||||
"a.a-button-text",
|
||||
"span.a-button-inner"
|
||||
]
|
||||
|
||||
for sel in locators:
|
||||
elements = page.locator(sel)
|
||||
count = await elements.count()
|
||||
for i in range(count):
|
||||
el = elements.nth(i)
|
||||
if await el.is_visible():
|
||||
txt = await el.inner_text()
|
||||
val = await el.get_attribute("value") or ""
|
||||
if "Continuer" in txt or "Continuer" in val:
|
||||
print(f"✅ Found candidate: {sel}")
|
||||
print(f" Text: {txt}")
|
||||
print(f" Value: {val}")
|
||||
print(f" OuterHTML: {await el.evaluate('el => el.outerHTML')}")
|
||||
else:
|
||||
print("No popup detected. Page title:", await page.title())
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
await browser.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in new issue
Block a user