Skip to main content

Welcome Contributors!

Thank you for your interest in improving the Price Tracker Bot! This guide will help you get started with contributing.

Code Structure

Understanding the codebase architecture will help you make effective contributions.

File Organization

The bot is a single-file application:
.
├── index.mjs          # Main bot code (~753 lines)
├── prices.json        # Data persistence (auto-generated)
├── .env              # Environment variables (token)
├── package.json      # Dependencies
└── README.md         # Project documentation

Code Sections

The code is organized into logical sections:
Purpose: Environment setup and bot initialization
const TELEGRAM_TOKEN = process.env.TELEGRAM_TOKEN;
const bot = new TelegramBot(TELEGRAM_TOKEN, { polling: true });
Key constants:
  • TELEGRAM_TOKEN - Bot authentication
  • bot - Main Telegram bot instance
Purpose: Data storage and retrievalKey components:
  • DATA_FILE = "prices.json" - Storage file
  • priceData - Object storing products by URL
  • chats - Set of registered chat IDs
  • loadData() - Reads from disk on startup
  • saveData() - Writes to disk after changes
Data structure:
{
  "products": {
    "[sanitized_url]": {
      url, title, price, lowestPrice,
      imageUrl, addedDate, addedBy,
      lastChecked, history: []
    }
  },
  "chats": [chatId1, chatId2, ...]
}
Purpose: Helper functionsFunctions:
  • sanitizeAmazonURL() - Removes query params from URLs
  • escapeMD() - Escapes Markdown special characters
  • HISTORY_LIMIT = 120 - Max history entries per product
Purpose: Web scraping logicMain function: scrapeProduct(page, url)Process:
  1. Navigate to product page (60s timeout)
  2. Wait 800ms for lazy-loaded content
  3. Try multiple CSS selectors for:
    • Title (6 selectors)
    • Price (6 selectors)
    • Image (4 selectors)
  4. Parse price string to number
  5. Return product data or error object
Selector arrays (lines 81-125):
// Title
["#productTitle", "h1#title", "h1.a-size-large", ...]

// Price  
["span.a-price .a-offscreen", "span#priceblock_ourprice", ...]

// Image
["#landingImage", "div#imgTagWrapperId img", ...]
Purpose: Automated price monitoringMain function: checkPrices()Process:
  1. Launch browser (shared page for efficiency)
  2. Iterate through all products
  3. Scrape current price
  4. Compare with stored price
  5. Detect drops: if (scraped.price < originalPrice)
  6. Update product data
  7. Record history (limit to 120 entries)
  8. Close browser
  9. Save data
  10. Send notifications to all chats
Error handling:
  • Logs errors but continues checking other products
  • Updates lastChecked even on failure
  • Collects errors for summary report
Purpose: Visual price historyFunctions:
  • generateChartBuffer() - Creates chart image using Playwright + Chart.js
  • sendPriceChart() - Sends chart to user
Process:
  1. Generate HTML with Chart.js
  2. Load in headless browser
  3. Wait for render (1s)
  4. Screenshot canvas element
  5. Return as buffer
  6. Send to Telegram as photo
Purpose: User interaction handlersCommand handlers:
  • /start (line 359) - Welcome message
  • /help (line 384) - Command reference
  • /add (line 398) - Add product to tracking
  • /check (line 470) - Force price check
  • /list (line 489) - Show all products
  • /stats (line 492) - Show statistics
  • /remove (line 520) - Delete product
  • /edit (line 545) - Update product URL
  • /chart (line 613) - Generate price graph
Pattern: All use bot.onText() with regex
Purpose: Handle inline button clicksHandled callbacks:
  • select_product:[url] - Show product details
  • delete_product:[url] - Remove product
  • edit_product:[url] - Start edit workflow
  • delete_all - Clear all products
  • list - Show product list
  • check_prices - Trigger check
  • chart:[url] - Generate chart
Pattern: bot.on("callback_query", ...)
Purpose: Scheduled automationJobs:
  • Daily summary at 20:00 (line 720)
  • Price checks every 2 hours (line 733)
Uses: node-cron library

Development Setup

Prerequisites

  • Node.js 16+
  • npm or yarn
  • Git
  • A Telegram account

Local Development

  1. Fork and clone:
    git clone https://github.com/YOUR_USERNAME/price-tracker-bot.git
    cd price-tracker-bot
    
  2. Install dependencies:
    npm install
    
  3. Install Playwright browsers:
    npx playwright install chromium
    
  4. Create test bot:
    • Message @BotFather on Telegram
    • Create a new bot for testing
    • Copy the token
  5. Configure environment:
    echo "TELEGRAM_TOKEN=your_test_bot_token" > .env
    
  6. Start development:
    node index.mjs
    
Use a separate test bot during development to avoid disrupting production users.

Testing Your Changes

Manual testing checklist:
  • Send /start - Verify welcome message
  • Add a product - Check scraping works
  • Run /list - Verify product appears
  • Run /check - Force a price update
  • Wait or modify prices.json - Test notifications
  • Try /chart - Verify chart generation
  • Test /edit - Update a product URL
  • Run /remove - Delete a product
  • Check console logs for errors
Testing scraper changes:
// Add temporary test code
const browser = await chromium.launch({ headless: false }); // See browser
const page = await browser.newPage();
const result = await scrapeProduct(page, "YOUR_TEST_URL");
console.log(result);
await browser.close();

How to Add Features

Adding New Commands

Example: Add a /pause command to temporarily stop notifications
  1. Add data structure (after line 22):
    let pausedChats = new Set();
    
  2. Update persistence (in loadData() and saveData()):
    // In loadData()
    pausedChats = new Set(saved.pausedChats || []);
    
    // In saveData()
    fs.writeFileSync(DATA_FILE, JSON.stringify({ 
      products: priceData, 
      chats: [...chats],
      pausedChats: [...pausedChats]
    }, null, 2));
    
  3. Add command handler (after line 623):
    bot.onText(/\/pause/, (msg) => {
      const chatId = msg.chat.id;
      if (pausedChats.has(chatId)) {
        pausedChats.delete(chatId);
        bot.sendMessage(chatId, "✅ Notificaciones reactivadas.");
      } else {
        pausedChats.add(chatId);
        bot.sendMessage(chatId, "⏸️ Notificaciones pausadas. Usa /pause para reactivar.");
      }
      saveData();
    });
    
  4. Update notification logic (in checkPrices(), line 223):
    for (const chatId of chats) {
      if (pausedChats.has(chatId)) continue; // Skip paused chats
      // ... rest of notification code
    }
    
  5. Update help text (line 378):
    ⏸️ /pause - Pausar/reactivar notificaciones
    

Adding New Scraper Selectors

If Amazon changes their HTML or you want to support more page types:
  1. Inspect the product page (browser DevTools)
  2. Find the CSS selector for the element
  3. Add to selector array (lines 81-125)
Example: Adding a new price selector
// Line 96 - price selectors
const priceText = await page.evaluate(() => {
  const selectors = [
    "span.a-price .a-offscreen",
    "span.a-offscreen",
    "span#priceblock_ourprice",
    "span#priceblock_dealprice",
    "div#corePrice_feature_div span.a-offscreen",
    'span[data-a-color="price"]',
    "span.a-color-price" // NEW SELECTOR
  ];
  // ... rest stays the same
});
Selectors are tried in order. Place most reliable/common selectors first for better performance.

Adding Export Functionality

Example: Export price data to CSV
  1. Add command (after line 623):
    bot.onText(/\/export/, async (msg) => {
      const chatId = msg.chat.id;
      
      let csv = "URL,Title,Current Price,Lowest Price,Added Date\n";
      
      Object.entries(priceData).forEach(([url, product]) => {
        csv += `"${url}","${product.title}",${product.price},${product.lowestPrice},"${product.addedDate}"\n`;
      });
      
      const buffer = Buffer.from(csv, 'utf-8');
      
      await bot.sendDocument(chatId, buffer, {
        filename: `price-tracker-export-${Date.now()}.csv`
      });
    });
    
  2. Update help text (line 378):
    📥 /export - Exportar datos a CSV
    

Adding Multi-User Support

To isolate products per user:
  1. Change data structure (line 21):
    // OLD:
    let priceData = {}; // keys: url -> product
    
    // NEW:
    let priceData = {}; // keys: chatId -> { url -> product }
    
  2. Update all product access throughout:
    // OLD:
    priceData[url]
    
    // NEW:
    if (!priceData[chatId]) priceData[chatId] = {};
    priceData[chatId][url]
    
  3. Update price check (line 139):
    async function checkPrices() {
      for (const [chatId, products] of Object.entries(priceData)) {
        // Check products for this chat
        // Notify only this chatId
      }
    }
    
This is a significant refactor. Test thoroughly and consider creating a migration script for existing data.

GitHub Workflow

Branching Strategy

# Create a feature branch
git checkout -b feature/your-feature-name

# Or a bugfix branch
git checkout -b fix/bug-description

Making Changes

  1. Write code following existing style
  2. Test thoroughly (see testing checklist above)
  3. Commit with clear messages:
    git add .
    git commit -m "Add pause command for notifications"
    

Submitting Pull Requests

  1. Push your branch:
    git push origin feature/your-feature-name
    
  2. Create PR on GitHub with:
    • Clear title describing the change
    • Description of what changed and why
    • Screenshots/examples if UI changes
    • Testing steps you performed
  3. PR template:
    ## Description
    [What does this PR do?]
    
    ## Changes
    - Added X feature
    - Fixed Y bug
    - Updated Z documentation
    
    ## Testing
    - [ ] Tested /command manually
    - [ ] Verified data persistence
    - [ ] Checked error handling
    - [ ] Reviewed console logs
    
    ## Screenshots
    [If applicable]
    
Link related issues in your PR description using Fixes #123 or Closes #456.

Code Style Guidelines

JavaScript Conventions

Current style (maintain consistency):
// Function declarations
function functionName() { ... }
async function asyncFunction() { ... }

// Arrow functions for callbacks
bot.onText(/pattern/, (msg) => { ... });

// Template literals for messages
const message = `Hello ${escapeMD(name)}`;

// Destructuring
const { price, title } = product;

// Error handling
try {
  // code
} catch (err) {
  console.error("❌ Error:", err.message);
}

Console Messages

Use emojis for visual clarity:
  • ✅ - Success
  • ❌ - Error
  • ⚠️ - Warning
  • ℹ️ - Info
  • 🎉 - Celebration
  • ⏳ - Loading/waiting
  • 🔄 - Processing
Examples:
console.log("✅ Datos cargados.");
console.error("❌ Error al cargar datos:", err.message);
console.warn("⚠️ Errores durante la revisión:", errors);
console.log("ℹ️ No hay productos para revisar.");

Telegram Messages

Use Markdown formatting:
// Bold for emphasis
*text*

// Code blocks
`/command`

// Links
[Text](https://url.com)

// MUST escape special characters
escape MD(userInput)

Common Pitfalls

Problem: Changes to priceData or chats not persistedSolution: Call saveData() after modifications
priceData[url] = newProduct;
saveData(); // DON'T FORGET THIS!
Problem: Memory leaks from unclosed browser instancesSolution: Always close in finally or after use
const browser = await chromium.launch();
try {
  // use browser
} finally {
  await browser.close(); // ALWAYS CLOSE
}
Problem: User input breaks Markdown formattingSolution: Use escapeMD() for all user data
// WRONG:
bot.sendMessage(chatId, `Title: ${product.title}`);

// RIGHT:
bot.sendMessage(chatId, `Title: ${escapeMD(product.title)}`);
Problem: Forgetting await on async operationsSolution: Mark functions as async and use await
// WRONG:
function handler(msg) {
  bot.sendMessage(chatId, "text"); // No await
  checkPrices(); // No await
}

// RIGHT:
async function handler(msg) {
  await bot.sendMessage(chatId, "text");
  await checkPrices();
}
Problem: Unhandled promise rejections crash botSolution: Wrap risky operations in try/catch
try {
  await riskyOperation();
} catch (err) {
  console.error("❌ Error:", err.message);
  // Inform user or continue gracefully
}

Documentation

When adding features, update:
  1. Code comments - Explain complex logic
  2. README.md - Add new commands to list
  3. Help command (line 384) - Include new commands
  4. This guide - Document architecture changes

Getting Help

If you need assistance:
  1. Check existing issues on GitHub
  2. Read the code - It’s well-structured and commented
  3. Open a discussion - Ask questions before major changes
  4. Join the community - Connect with other contributors
No contribution is too small! Bug reports, documentation improvements, and feature suggestions are all valuable.

Recognition

All contributors will be acknowledged in:
  • Project README
  • Release notes
  • GitHub contributors page
Thank you for helping improve Price Tracker Bot! 🎉

Build docs developers (and LLMs) love