← 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;