Files
deal-hunter/backend/app.py
2025-10-30 04:04:14 -07:00

97 lines
4.6 KiB
Python

from fastapi import FastAPI
from pydantic import BaseModel
from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
import requests
from openai import OpenAI
import random
from datetime import datetime
import pandas as pd
import io
import os
import time
app = FastAPI(title="Deal Hunter Backend")
client = OpenAI(api_key=os.getenv("GROK_API_KEY"), base_url="https://api.x.ai/v1")
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
class SearchRequest(BaseModel):
item: str = "iPhone 14 Pro Max"
location_radius: int = 50
min_price: float = 0
max_price: float = 1000
condition: str = "Any"
min_score: int = 7
@app.post("/hunt")
def hunt_deals(req: SearchRequest):
# Scrape FB
ua = random.choice([
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
])
url = f"https://www.facebook.com/marketplace/search/?query={req.item.replace(' ', '+')}&radius={req.location_radius}"
listings = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(user_agent=ua)
page.goto(url, wait_until="networkidle")
time.sleep(5)
for _ in range(3):
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
time.sleep(3)
soup = BeautifulSoup(page.content(), 'html.parser')
articles = soup.find_all('div', {'role': 'article'})[:20]
for article in articles:
try:
title = article.find('span', class_='x1lliihq').text.strip() or 'N/A'
price_elem = article.find('span', attrs={'aria-label': lambda v: v and '$' in v})
price = float(price_elem['aria-label'].replace('$', '').replace(',', '')) if price_elem else 0
if req.min_price <= price <= req.max_price:
link = 'https://fb.com' + article.find('a')['href'] if article.find('a') else 'N/A'
img = article.find('img')['src'] if article.find('img') else 'N/A'
cond = 'New' if 'new' in title.lower() else 'Used'
if req.condition == "Any" or cond == req.condition:
listings.append({'title': title, 'price': price, 'link': link, 'image': img, 'condition': cond})
except:
continue
browser.close()
# Value & Score
deals = []
for listing in listings:
query = listing['title'].replace(' ', '+')
ebay_url = f"https://www.ebay.com/sch/i.html?_nkw={query}&LH_Sold=1&LH_Complete=1&rt=nc"
response = requests.get(ebay_url, headers={'User-Agent': ua}, timeout=10)
soup = BeautifulSoup(response.text, 'html.parser')
prices = [float(span.text.replace('$', '').replace(',', '')) for span in soup.find_all('span', class_='s-item__price')[:5] if span.text.replace('$', '').replace(',', '').replace('.', '').isdigit()]
resale = sum(prices) / len(prices) if prices else 0
offer = resale * (1 - 0.3)
score = min(10, (resale - listing['price']) / resale * 10) + (2 if listing['condition'] == 'New' else 0) if resale > 0 else 0
status = 'Hot Deal' if score >= req.min_score else 'Pass'
# AI Offer
prompt = f"Polite lowball offer for {listing['title']} at ${listing['price']}, condition {listing['condition']}, resale ${resale}. Offer ${offer}."
response = client.chat.completions.create(model="grok-beta", messages=[{"role": "user", "content": prompt}])
ai_offer = response.choices[0].message.content
deal = {**listing, 'resale_avg': resale, 'offer': offer, 'score': score, 'status': status, 'ai_offer': ai_offer}
deals.append(deal)
if status == 'Hot Deal':
caption = f"*Hot Deal!*\n*{listing['title']}*\nPrice: ${listing['price']}\nOffer: ${offer}\nScore: {score}/10\n{ai_offer}\n[Link]({listing['link']})"
requests.post(f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendPhoto", data={'chat_id': TELEGRAM_CHAT_ID, 'caption': caption, 'parse_mode': 'Markdown'}, files={'photo': requests.get(listing['image']).content} if listing['image'].startswith('http') else {})
# CSV
df = pd.DataFrame(deals)
csv_buffer = io.StringIO()
df.to_csv(csv_buffer, index=False)
csv_data = csv_buffer.getvalue()
return {"deals": deals, "csv": csv_data, "hot_count": len([d for d in deals if d['status'] == 'Hot Deal'])}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)