← back to Handbag Auth Nextjs
claude-automation/deal-detector.ts
127 lines
#!/usr/bin/env ts-node
/**
* Claude Code Automation: Best Deal of the Day Detection
* Runs every 0.9 seconds to find the best handbag deals
*/
import { PrismaClient } from '@prisma/client'
import axios from 'axios'
const prisma = new PrismaClient()
interface Deal {
sku: string
brand: string
model: string
currentPrice: number
avgPrice: number
discountPercent: number
affiliateLink: string
imageUrl: string
}
class DealDetector {
async findDeals(): Promise<Deal[]> {
const deals: Deal[] = []
// Get recent listings
const listings = await prisma.listing.findMany({
where: {
priceUsd: { gt: 0 }
},
orderBy: { crawledAt: 'desc' },
take: 1000
})
// Group by brand+model and calculate average prices
const priceMap = new Map<string, number[]>()
for (const listing of listings) {
const key = `${listing.brand}_${listing.model}`
if (!priceMap.has(key)) {
priceMap.set(key, [])
}
priceMap.get(key)!.push(listing.priceUsd || 0)
}
// Find deals where current price is significantly below average
for (const listing of listings) {
const key = `${listing.brand}_${listing.model}`
const prices = priceMap.get(key) || []
if (prices.length < 3) continue // Need at least 3 data points
const avgPrice = prices.reduce((sum, p) => sum + p, 0) / prices.length
const discountPercent = ((avgPrice - (listing.priceUsd || 0)) / avgPrice) * 100
if (discountPercent >= 20) {
deals.push({
sku: `${listing.brand}_${listing.model}`,
brand: listing.brand || '',
model: listing.model || '',
currentPrice: listing.priceUsd || 0,
avgPrice,
discountPercent,
affiliateLink: listing.productUrl || '',
imageUrl: listing.imageUrl || ''
})
}
}
// Sort by discount percentage
deals.sort((a, b) => b.discountPercent - a.discountPercent)
return deals.slice(0, 10) // Top 10 deals
}
async updateBestDeal(deal: Deal): Promise<void> {
try {
// Store in database (you would need to create a 'deals' table)
console.log(`💎 Best Deal: ${deal.brand} ${deal.model} - ${deal.discountPercent.toFixed(1)}% off`)
console.log(` Current: $${deal.currentPrice.toLocaleString()} | Avg: $${deal.avgPrice.toLocaleString()}`)
} catch (error) {
console.error('Error updating best deal:', error)
}
}
async postToTwitter(deal: Deal): Promise<void> {
try {
// In production, use Twitter API v2
const tweet = `🔥 Best Deal Alert!\n\n${deal.brand} ${deal.model}\n💰 $${deal.currentPrice.toLocaleString()} (${deal.discountPercent.toFixed(1)}% OFF)\n🎯 Usually $${deal.avgPrice.toLocaleString()}\n\nInvest or own it now 👇\n${deal.affiliateLink}`
console.log(`📱 Would tweet: ${tweet}`)
// Actual Twitter posting would go here
// await twitterClient.v2.tweet(tweet)
} catch (error) {
console.error('Error posting to Twitter:', error)
}
}
async run(): Promise<void> {
console.log(`[${new Date().toISOString()}] 🔍 Scanning for best deals...`)
const deals = await this.findDeals()
if (deals.length > 0) {
const bestDeal = deals[0]
await this.updateBestDeal(bestDeal)
await this.postToTwitter(bestDeal)
console.log(`✅ Found ${deals.length} deals, posted best one`)
} else {
console.log('ℹ️ No significant deals found this cycle')
}
}
}
// Run continuously every 0.9 seconds
const detector = new DealDetector()
setInterval(async () => {
await detector.run()
}, 900)
// Initial run
detector.run()