Skip to content

Latest commit

 

History

History
234 lines (174 loc) · 8.04 KB

File metadata and controls

234 lines (174 loc) · 8.04 KB

Whey Promotion Bot

🇧🇷 Português  |  🇺🇸 English

A bot that monitors whey protein prices across 8 Brazilian online stores, calculates the best cost per gram of protein, and automatically sends alerts via Telegram and WhatsApp — with messages generated by AI (Groq / Llama 3.3 70B).


What it does

  • Collects prices twice a day (8:00 AM and 8:00 PM, Brasília time) from 8 stores
  • Calculates a ranking by cost per gram of protein — the lower, the better
  • Automatically detects promotions: if a product drops ≥ 5% below its 7-day average, an alert is fired
  • Sends a daily ranking at 8:05 AM with each product's photo and purchase link
  • Sends price-band offers from Growth Supplements (12:00 PM) and ProFit Labs (4:00 PM)
  • Sends flash sales from Soldiers Nutrition (8:10 PM)
  • AI-generated messages: every product goes through Groq (Llama 3.3 70B) before being sent — with tone calibrated by ranking position and automatically detected whey type
  • REST API protected by API Key to query the ranking and trigger dispatches manually

Stack

Layer Technology
Language Java 17
Framework Spring Boot 3.3.5
HTTP Client Spring WebFlux (WebClient)
Scheduling Spring Scheduler (@Scheduled)
Database PostgreSQL
ORM Spring Data JPA / Hibernate
Message generation Groq API (Llama 3.3 70B)
Notifications Telegram Bot API + Baileys sidecar (WhatsApp)
Containerization Docker + Docker Compose
Deployment Railway

How the ranking is calculated

cost per gram of protein = price / total protein in package (g)

Total protein comes from an internal nutrition table (nutrition_info), matched by product name, brand, and package weight. The lower the value, the better the deal.


How promotions are detected

On every collection run, the service compares the current price against the 7-day moving average from price_history. If the drop is ≥ 5% and there are at least 3 samples in the window, an alert is fired with the product photo, discount percentage, and purchase link. The threshold and window are configurable via environment variables.


Full flow

Scheduler
    │
    ├─ 08:00 / 20:00 ──► StoreCollectorService (8 stores)
    │                         └─ OfferPersistenceService (upsert + price_history)
    │                         └─ RankingService (calculates cost/g protein)
    │                         └─ PromotionService (compares vs 7-day average)
    │                               └─ GroqMessageService (generates caption via AI)
    │                                     ├─ TelegramNotificationService
    │                                     └─ WhatsAppNotificationService → Baileys sidecar
    │
    ├─ 08:05 ──► Daily ranking (Telegram + WhatsApp)
    ├─ 12:00 ──► Growth price-band offers (Telegram + WhatsApp)
    ├─ 16:00 ──► ProFit Labs promotions (Telegram + WhatsApp)
    └─ 20:10 ──► Soldiers Nutrition flash sale (Telegram + WhatsApp)

Endpoints

All endpoints require the X-API-Key header.

Persisted data

GET  /api/health
GET  /api/products
GET  /api/products/available
GET  /api/products/by-store?store=GROWTH

GET  /api/rankings/whey/top-cost-benefit?top=10
GET  /api/rankings/whey/top-cost-benefit?top=5&store=DARK_LAB

Live collection

GET  /api/growth/offers
GET  /api/growth/ofertas
GET  /api/growth/ofertas/bands
GET  /api/darklab/offers
GET  /api/profitlabs/offers
GET  /api/profitlabs/promocoes
GET  /api/profitlabs/promocoes/bands
GET  /api/soldiers/offers
GET  /api/soldiers/oferta-relampago
GET  /api/blackskull/offers
GET  /api/nutrata/offers
GET  /api/adaptogen/offers
GET  /api/absolut/offers
GET  /api/offers/whey

Manual trigger — Telegram

POST /api/telegram/trigger/ranking?top=10
POST /api/telegram/trigger/promotions
POST /api/telegram/trigger/growth-ofertas
POST /api/telegram/trigger/profitlabs-promocoes
POST /api/telegram/trigger/soldiers-relampago

Manual trigger — WhatsApp

POST /api/whatsapp/trigger/ranking?top=10
POST /api/whatsapp/trigger/promotions
POST /api/whatsapp/trigger/growth/ofertas
POST /api/whatsapp/trigger/profitlabs/promocoes
POST /api/whatsapp/trigger/soldiers/oferta-relampago

Running locally

Requirements: Docker, Java 17+, Maven 3.9+

# 1. Start the database
docker-compose up -d

# 2. Run the application
mvn spring-boot:run

The API starts at http://localhost:8080. On the first run, StartupCollector automatically triggers a data collection if the database is empty. Wait ~30 seconds and query the ranking:

GET http://localhost:8080/api/rankings/whey/top-cost-benefit?top=10

Environment variables

Variable Description Default
SPRING_DATASOURCE_URL PostgreSQL JDBC URL jdbc:postgresql://localhost:5432/whey_db
SPRING_DATASOURCE_USERNAME Database user whey_user
SPRING_DATASOURCE_PASSWORD Database password whey_pass
API_KEY Key to protect API endpoints (empty = no protection)
TELEGRAM_BOT_TOKEN Telegram bot token (empty = no sending)
TELEGRAM_CHAT_ID Target group or channel ID
WHATSAPP_BASE_URL Baileys sidecar base URL (empty = no sending)
WHATSAPP_PHONE Target JID (channel @newsletter, group @g.us or number)
GROQ_API_KEY Groq API key (AI messages) (empty = uses fixed templates)
PORT HTTP port 8080

Deploying to Railway

The project uses two Railway services: the Java bot and the WhatsApp sidecar.

Java bot

  1. New Project → Deploy from GitHub repo
  2. Add a PostgreSQL plugin
  3. Set the environment variables in the Railway dashboard
  4. Railway builds and deploys automatically on every push to main

Baileys sidecar (WhatsApp)

The sidecar is a Node.js service that maintains the WhatsApp connection via Baileys.

  1. New Service → Deploy from GitHub repo (baileys-sidecar/ folder)
  2. Add a Volume mounted at /app/auth to persist the session across deploys
  3. After deploy, open GET /qr at the service's public URL and scan the QR code from WhatsApp
  4. Set WHATSAPP_BASE_URL in the Java bot pointing to the sidecar's internal URL

The session is stored in the volume and survives redeploys. The QR code only needs to be scanned again if the session expires.


Project structure

src/main/java/com/devlil0/whey_promotion_bot/
├── client/      # HTTP clients per store (8 stores)
├── config/      # WebClient, API Key interceptor, nutrition seeder
├── controller/  # REST endpoints, Telegram and WhatsApp triggers
├── dto/         # ProductOfferResponse, RankingItemResponse, PromotionAlert, etc.
├── entity/      # JPA: ProductOffer, NutritionInfo, ProductScore, PriceHistory
├── repository/  # Spring Data JPA repositories
├── scheduler/   # Twice-daily collection + dispatch schedules
└── service/     # Ranking, promotions, collection, notifications, AI (Groq), nutrition matching

baileys-sidecar/  # Node.js service for WhatsApp (Baileys)
├── index.js      # Express + Baileys: /send-text, /send-image, /health, /qr
├── Dockerfile
└── railway.toml

Database schema

Table Description
product_offer Products collected from stores with price, availability, and URL
nutrition_info Nutrition table: protein per serving, total per package
product_score Calculated ranking: cost/g of protein + position
price_history Price snapshot on every collection run — basis for the 7-day average

Schema is created and updated automatically by Hibernate (ddl-auto: update).


Promotion settings

Parameter Default Description
promotion.discount-threshold 0.05 Minimum drop relative to the average (5%)
promotion.history-days 7 Average window in days
promotion.min-history-samples 3 Minimum samples required to trigger an alert