← back to Handbag Authentication
ai/slack-notifier.js
176 lines
require('dotenv').config();
const axios = require('axios');
class SlackNotifier {
constructor(webhookUrl) {
this.webhookUrl = webhookUrl || process.env.SLACK_WEBHOOK_URL;
}
async notifyDeal(listing, dealAnalysis) {
if (!this.webhookUrl) {
console.log('Slack webhook not configured, skipping notification');
return;
}
const message = this.formatDealMessage(listing, dealAnalysis);
try {
await axios.post(this.webhookUrl, message);
console.log(`✓ Slack notification sent for ${listing.title}`);
} catch (error) {
console.error('Error sending Slack notification:', error.message);
}
}
formatDealMessage(listing, analysis) {
const dealEmoji = analysis.deal_percentage >= 50 ? '🔥🔥🔥' : analysis.deal_percentage >= 30 ? '🔥🔥' : '🔥';
const profit = Math.round(analysis.avg_us_price - listing.price_usd - 150); // minus costs
return {
text: `<!channel> Hey <@Steve Abrams (DW)>! ${dealEmoji} NEW DEAL ALERT: ${listing.brand} - ${analysis.deal_percentage.toFixed(0)}% off - Profit: $${profit}+`,
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: `${dealEmoji} HIGH VALUE DEAL ALERT FOR STEVE!`,
emoji: true
}
},
{
type: 'section',
fields: [
{
type: 'mrkdwn',
text: `*Brand:*\n${listing.brand || 'Unknown'}`
},
{
type: 'mrkdwn',
text: `*Deal:*\n${analysis.deal_percentage.toFixed(1)}% below market`
},
{
type: 'mrkdwn',
text: `*JP Price:*\n¥${listing.price_jpy.toLocaleString()} ($${listing.price_usd})`
},
{
type: 'mrkdwn',
text: `*US Market:*\n$${analysis.avg_us_price.toFixed(0)} avg`
},
{
type: 'mrkdwn',
text: `*Net Profit:*\n$${profit}+ after costs`
},
{
type: 'mrkdwn',
text: `*Source:*\n${listing.source}`
},
{
type: 'mrkdwn',
text: `*Model:*\n${listing.model || 'See title'}`
},
{
type: 'mrkdwn',
text: `*Size:*\n${listing.size || 'N/A'}`
},
{
type: 'mrkdwn',
text: `*Color:*\n${listing.color || 'N/A'}`
},
{
type: 'mrkdwn',
text: `*Material:*\n${listing.material || 'N/A'}`
},
{
type: 'mrkdwn',
text: `*Listing Type:*\n${listing.listing_type === 'auction' ? '⚡ Auction' : '🛒 Buy Now'}`
},
{
type: 'mrkdwn',
text: `*Ends:*\n${listing.auction_end_date || 'N/A'}`
}
]
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*${listing.title}*\n${analysis.ai_notes || ''}`
}
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Condition:* ${listing.condition} | *Confidence:* ${(analysis.confidence_score * 100).toFixed(0)}%`
}
},
{
type: 'actions',
elements: [
{
type: 'button',
text: {
type: 'plain_text',
text: 'View Listing',
emoji: true
},
url: listing.product_url,
style: 'primary'
}
]
},
{
type: 'divider'
}
]
};
}
async notifyDailySummary(stats) {
if (!this.webhookUrl) return;
const message = {
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: '📊 Daily Arbitrage Summary',
emoji: true
}
},
{
type: 'section',
fields: [
{
type: 'mrkdwn',
text: `*Total Listings:*\n${stats.total_listings}`
},
{
type: 'mrkdwn',
text: `*New Deals:*\n${stats.new_deals}`
},
{
type: 'mrkdwn',
text: `*Best Deal:*\n${stats.best_deal_percentage}% off`
},
{
type: 'mrkdwn',
text: `*Avg Deal:*\n${stats.avg_deal_percentage}% off`
}
]
}
]
};
try {
await axios.post(this.webhookUrl, message);
console.log('✓ Daily summary sent to Slack');
} catch (error) {
console.error('Error sending Slack summary:', error.message);
}
}
}
module.exports = SlackNotifier;