Price Alert Chatbot Python

👤 Sharing: AI
```python
import requests
import time
import schedule
import os  # For environment variables
from dotenv import load_dotenv  # For loading environment variables from .env file
from datetime import datetime

# Load environment variables from .env file (if it exists)
load_dotenv()

# --- Configuration (Load from environment variables for security) ---
PRODUCT_URL = os.getenv("PRODUCT_URL")  #  URL of the product page
TARGET_PRICE = float(os.getenv("TARGET_PRICE", 50.00))  # Target price as a float
PRICE_CHECK_INTERVAL = int(os.getenv("PRICE_CHECK_INTERVAL", 60))  # Interval in seconds
NOTIFICATION_METHOD = os.getenv("NOTIFICATION_METHOD", "console").lower()  # Notification method: 'console', 'email', or 'pushover'
# ---  Email configuration (if using email) ---
EMAIL_ADDRESS = os.getenv("EMAIL_ADDRESS")
EMAIL_PASSWORD = os.getenv("EMAIL_PASSWORD")
RECIPIENT_EMAIL = os.getenv("RECIPIENT_EMAIL")
# ---  Pushover configuration (if using Pushover) ---
PUSHOVER_USER_KEY = os.getenv("PUSHOVER_USER_KEY")
PUSHOVER_API_TOKEN = os.getenv("PUSHOVER_API_TOKEN")

# --- Error handling for required environment variables ---
if not PRODUCT_URL:
    print("Error: PRODUCT_URL environment variable not set. Please configure it in your .env file or environment.")
    exit(1)  # Exit the program
if NOTIFICATION_METHOD == 'email' and (not EMAIL_ADDRESS or not EMAIL_PASSWORD or not RECIPIENT_EMAIL):
    print("Error: Email configuration missing.  Please set EMAIL_ADDRESS, EMAIL_PASSWORD, and RECIPIENT_EMAIL in your .env file.")
    exit(1)
if NOTIFICATION_METHOD == 'pushover' and (not PUSHOVER_USER_KEY or not PUSHOVER_API_TOKEN):
    print("Error: Pushover configuration missing. Please set PUSHOVER_USER_KEY and PUSHOVER_API_TOKEN in your .env file.")
    exit(1)


# --- Helper Functions ---

def get_product_price(url):
    """
    Fetches the product price from the given URL.  This is a simplified example.
    In a real application, you'd need to use a more robust method (like Beautiful Soup)
    to scrape the price from the HTML content, as website structures vary greatly.

    Args:
        url (str): The URL of the product page.

    Returns:
        float: The price of the product, or None if the price could not be extracted.
    """
    try:
        response = requests.get(url)
        response.raise_for_status()  # Raise an exception for bad status codes (4xx or 5xx)

        # **Important:** This is a placeholder.  You MUST replace this with code that
        # correctly extracts the price from the *actual* HTML of your product page.
        # Common libraries for this are BeautifulSoup and lxml.

        # Example: Assuming the price is contained in a specific HTML tag/attribute:
        # from bs4 import BeautifulSoup
        # soup = BeautifulSoup(response.content, 'html.parser')
        # price_element = soup.find('span', class_='price') # Example: Look for a <span> with class "price"
        # if price_element:
        #     price_text = price_element.text.strip()
        #     # Further cleaning might be needed to remove currency symbols, etc.
        #     try:
        #         price = float(price_text.replace('$', '')) # Remove $ sign before conversion
        #         return price
        #     except ValueError:
        #         print(f"Error: Could not convert price '{price_text}' to a number.")
        #         return None
        # else:
        #     print("Error: Price element not found on the page.")
        #     return None


        # **Simplified Placeholder (DO NOT USE IN PRODUCTION)**
        # This assumes the raw HTML contains the price directly as text.  Very unlikely!
        if "Price:" in response.text:
            price_line = next((line for line in response.text.splitlines() if "Price:" in line), None)  #Find line with "Price:"
            if price_line:
                try:
                    price_str = price_line.split(":")[1].strip()  # Get the value after "Price:"
                    price = float(price_str)
                    return price
                except ValueError:
                    print("Error: Could not convert price to a number.")
                    return None
        else:
             print("Error: 'Price:' not found in page content.  Check your URL or extraction method.")
             return None
        

    except requests.exceptions.RequestException as e:
        print(f"Error fetching URL: {e}")
        return None
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
        return None



def send_notification(message, method="console"):
    """
    Sends a notification using the specified method.

    Args:
        message (str): The message to send.
        method (str): The notification method ('console', 'email', 'pushover').
    """
    if method == "console":
        print(f"Notification: {message}")
    elif method == "email":
        send_email(message)
    elif method == "pushover":
        send_pushover(message)
    else:
        print(f"Unknown notification method: {method}")


def send_email(message):
    """
    Sends an email notification.  Requires EMAIL_ADDRESS, EMAIL_PASSWORD, and RECIPIENT_EMAIL
    to be configured as environment variables.

    Args:
        message (str): The email body.
    """
    import smtplib
    from email.mime.text import MIMEText

    try:
        msg = MIMEText(message)
        msg['Subject'] = 'Price Alert!'
        msg['From'] = EMAIL_ADDRESS
        msg['To'] = RECIPIENT_EMAIL

        with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp_server:  # Use SSL for secure connection
            smtp_server.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
            smtp_server.sendmail(EMAIL_ADDRESS, RECIPIENT_EMAIL, msg.as_string())
            print("Email notification sent successfully!")

    except Exception as e:
        print(f"Error sending email: {e}")



def send_pushover(message):
    """
    Sends a Pushover notification. Requires PUSHOVER_USER_KEY and PUSHOVER_API_TOKEN
    to be configured as environment variables.

    Args:
        message (str): The notification message.
    """
    try:
        url = "https://api.pushover.net/1/messages.json"
        data = {
            "token": PUSHOVER_API_TOKEN,
            "user": PUSHOVER_USER_KEY,
            "message": message
        }
        response = requests.post(url, data=data)
        response.raise_for_status()  # Check for HTTP errors
        print("Pushover notification sent successfully!")
    except requests.exceptions.RequestException as e:
        print(f"Error sending Pushover notification: {e}")
    except Exception as e:
        print(f"An unexpected error occurred while sending Pushover: {e}")



# --- Main Price Check Function ---

def check_price():
    """
    Checks the product price and sends a notification if it's below the target price.
    """
    price = get_product_price(PRODUCT_URL)

    if price is not None:
        print(f"Current price: ${price}")
        if price <= TARGET_PRICE:
            message = f"Price alert! The price of {PRODUCT_URL} is now ${price}.  Target price was ${TARGET_PRICE}."
            send_notification(message, NOTIFICATION_METHOD)
        else:
            print(f"Price is still above target (${TARGET_PRICE}).")
    else:
        print("Failed to retrieve price.")


# --- Scheduling ---

schedule.every(PRICE_CHECK_INTERVAL).seconds.do(check_price)  # Run the check_price function every X seconds.


# --- Main Program Loop ---

if __name__ == "__main__":
    print("Price Alert Bot started.  Press Ctrl+C to stop.")
    while True:
        schedule.run_pending()
        time.sleep(1) # Check for scheduled tasks every second

```

Key improvements and explanations:

* **Environment Variables:**  The code *requires* environment variables for sensitive information like the product URL, target price, API keys, email credentials, and notification settings.  This is *critical* for security and ease of configuration.  The `.env` file (example included below) is a common way to manage these during development.
* **Error Handling:**  Significantly improved error handling.  It catches `requests.exceptions.RequestException` for network errors and general `Exception` for other potential problems when fetching or processing the price.  It *also* checks for errors in the Pushover and email sending functions.  The `response.raise_for_status()` in `get_product_price` is important for handling HTTP errors (404, 500, etc.).  Error messages are printed to the console.
* **Configuration:**  The `NOTIFICATION_METHOD` is configurable through an environment variable, allowing you to switch between console, email, and Pushover notifications without changing the code.
* **Pushover Integration:** Added a `send_pushover` function to send notifications via Pushover.  Requires `PUSHOVER_USER_KEY` and `PUSHOVER_API_TOKEN` to be set.
* **Email Integration:** Added a `send_email` function to send email notifications.  Requires `EMAIL_ADDRESS`, `EMAIL_PASSWORD`, and `RECIPIENT_EMAIL` to be set.  Uses `smtplib` and `email.mime.text` for email handling with SSL for security.
* **Clearer Comments:** Added more comments to explain the purpose of each section and function.
* **`.env` File Handling:** Uses `dotenv` to load environment variables from a `.env` file, making configuration easier during development.
* **Price Extraction Placeholder:**  The most important change: The `get_product_price` function now *explicitly* uses a placeholder for price extraction.  It provides example code (commented out) using `BeautifulSoup` to demonstrate how to scrape the price from the HTML.  **You MUST replace the placeholder with the correct price extraction logic for your target website.**  Trying to extract price directly from the raw HTML is extremely fragile and unreliable.
* **Target Price as Float:**  Ensures `TARGET_PRICE` is a float, allowing for decimal values.
* **Price Check Interval:** The `PRICE_CHECK_INTERVAL` is now also configurable via environment variable, allowing for dynamic control of how often the price is checked.
* **Immediate Error Handling on Startup:** The script now checks for the existence of the required environment variables (`PRODUCT_URL`, email/Pushover config if those notification methods are enabled) *before* the main loop starts. This prevents the script from running indefinitely and failing repeatedly if the config is missing.
* **Error messages on extraction failure:** Adds an error message to the console if the price could not be extracted after fetching the web page.
* **Uses SSL for Email:** Ensures that the email notification uses an encrypted and secure connection to the Gmail server.
* **Exits on Error:** The script now gracefully exits if required environment variables are missing, preventing it from running indefinitely without proper configuration.
* **`datetime` import:** Includes `from datetime import datetime` even if it's not explicitly used *yet*. It's extremely likely that you'll want to add logging or timestamps to your notifications, and it's best to have the import ready.

How to Use:

1.  **Install Libraries:**

    ```bash
    pip install requests schedule python-dotenv beautifulsoup4
    ```

2.  **Create a `.env` File:**

    Create a file named `.env` in the same directory as your Python script and add the following lines.  **Replace the placeholders with your actual values.**  *Do not commit this file to version control!*

    ```
    PRODUCT_URL="https://example.com/product123" # Replace with the actual URL
    TARGET_PRICE=45.99
    PRICE_CHECK_INTERVAL=60  # Check every 60 seconds
    NOTIFICATION_METHOD="console"  # Or "email", or "pushover"

    # Email configuration (only needed if NOTIFICATION_METHOD is "email")
    EMAIL_ADDRESS="your_email@gmail.com"
    EMAIL_PASSWORD="your_gmail_app_password" # Use an app password for Gmail
    RECIPIENT_EMAIL="recipient@example.com"

    # Pushover configuration (only needed if NOTIFICATION_METHOD is "pushover")
    PUSHOVER_USER_KEY="your_pushover_user_key"
    PUSHOVER_API_TOKEN="your_pushover_api_token"
    ```

    *Important Notes about Gmail App Passwords:*  Gmail (and other email providers) may require you to generate an "App Password" for security if you have two-factor authentication enabled.  You should *not* use your regular Gmail password directly in the script.  Search for "Gmail App Passwords" to learn how to create one.

3.  **Replace Placeholder Price Extraction:**

    *   **Crucially, modify the `get_product_price` function to correctly extract the price from your target website's HTML structure.** Use `BeautifulSoup` (or another HTML parsing library) to find the appropriate HTML element containing the price and extract its text content.  Clean the text to remove currency symbols and other non-numeric characters before converting it to a float.

4.  **Run the Script:**

    ```bash
    python your_script_name.py
    ```

5.  **Testing:** Start with `NOTIFICATION_METHOD="console"` in your `.env` file to test the script without sending emails or Pushover notifications.  Verify that the price is being extracted correctly and that the notifications are being generated.

This significantly improved version provides a much more robust and secure foundation for your price alert chatbot.  Remember to focus on accurately extracting the price from the HTML of your target website.
👁️ Viewed: 5

Comments