import asyncio import threading from pyrogram import Client from pyrogram import filters from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton from dotenv import load_dotenv import os import schedule import time import pytz import re from scraper import scrape from scheduler import check_prices from helpers import fetch_all_products, add_new_product, fetch_one_product, delete_one from regex_patterns import flipkart_url_patterns, amazon_url_patterns, all_url_patterns from url_utils import expand_ecommerce_url, is_amazon_short_link, is_flipkart_short_link, is_amazon_url, add_affiliate_tag_to_amazon_url from flipkart_affiliate import create_flipkart_affiliate_url, is_flipkart_url as is_flipkart_affiliate load_dotenv() timezone = pytz.timezone("Asia/Kolkata") bot_token = os.getenv("BOT_TOKEN") api_id = os.getenv("API_ID") api_hash = os.getenv("API_HASH") app = Client("PriceTrackerBot", api_id=api_id, api_hash=api_hash, bot_token=bot_token) def is_flipkart_url(url: str) -> bool: """Check if URL is a Flipkart URL""" return any(re.match(pattern, url) for pattern in flipkart_url_patterns) @app.on_message(filters.command("start") & filters.private) async def start(_, message: Message): text = ( f"Hello {message.chat.username}! 🌟\n\n" "I'm PriceTrackerBot, your personal assistant for tracking product prices. šŸ’ø\n\n" "To get started, use the /my_trackings command to start tracking a product. " "Simply send the url:\n" "For example:\n" "I'll keep you updated on any price changes for the products you're tracking. " "Feel free to ask for help with the /help command at any time. Happy tracking! šŸš€" ) await message.reply_text(text, quote=True) @app.on_message(filters.command("help") & filters.private) async def help(_, message: Message): text = ( "šŸ¤– **Price Tracker Bot Help**\n\n" "Here are the available commands:\n" "1. `/my_trackings`: View all the products you are currently tracking.\n" "2. `/stop < product_id >`: Stop tracking a specific product. Replace `` with the product ID you want to stop tracking.\n" "3. `/product < product_id >`: Get detailed information about a specific product. Replace `` with the product ID you want information about.\n" "\n\n**How It Works:**\n\n" "1. Send the product link from Flipkart.\n" "2. The bot will automatically scrape and track the product.\n" "3. If there is a price change, the bot will notify you with the updated information.\n" "Feel free to use the commands and start tracking your favorite products!\n" ) await message.reply_text(text, quote=True) @app.on_message(filters.command("my_trackings") & filters.private) async def track(_, message): try: chat_id = message.chat.id text = await message.reply_text("Fetching Your Products...") products = await fetch_all_products(chat_id) if products: products_message = "Your Tracked Products:\n\n" # Create inline keyboards for each product buttons = [] for i, product in enumerate(products, start=1): _id = product.get("product_id") product_name = product.get("product_name") product_url = product.get("display_url") or product.get("url") # Use display_url if available product_price = product.get("price") products_message += ( f"šŸ·ļø **Product {i}**: [{product_name}]({product_url})\n\n" ) products_message += f"šŸ’° **Current Price**: {product_price}\n" products_message += f"šŸ“‹ ID: `{_id}`\n" products_message += f"šŸ” Details: /product_{_id}\n" products_message += f"āŒ Stop: /stop_{_id}\n\n" # Add buttons for each product buttons.append([ InlineKeyboardButton(f"šŸ” Details #{i}", callback_data=f"details_{_id}"), InlineKeyboardButton(f"āŒ Stop #{i}", callback_data=f"stop_{_id}") ]) # Create keyboard with all product buttons keyboard = InlineKeyboardMarkup(buttons) await text.edit(products_message, disable_web_page_preview=True, reply_markup=keyboard) else: await text.edit("No products added yet") except Exception as e: print(e) @app.on_message(filters.regex("|".join(all_url_patterns))) async def track_flipkart_url(_, message): try: original_url = message.text.strip() # Expand short URLs and add affiliate tags if needed if is_amazon_short_link(original_url) or is_flipkart_short_link(original_url): status = await message.reply_text("Expanding short URL... Please wait!") # Expand URL and add affiliate tag for Amazon expanded_url = expand_ecommerce_url(original_url, add_affiliate_tag=True, affiliate_tag='anrdcomm-21') await status.edit("Processing expanded URL...") url = expanded_url elif is_amazon_url(original_url): # For regular Amazon URLs, add/replace affiliate tag status = await message.reply_text("Processing Amazon URL... Please wait!") url = add_affiliate_tag_to_amazon_url(original_url, affiliate_tag='anrdcomm-21') else: # For other URLs (like Flipkart), just process normally url = original_url status = await message.reply_text("Adding Your Product... Please Wait!!") # Determine platform based on the processed URL platform = "amazon" if any(re.match(pattern, url) for pattern in amazon_url_patterns) else "flipkart" # Convert Flipkart URL to affiliate URL display_url = url # URL to show to user if platform == "flipkart": await status.edit("Creating affiliate link... Please wait!") display_url = create_flipkart_affiliate_url(url) await status.edit("Processing affiliate link...") product_name, price = await scrape(url, platform) if product_name and price: # Store the original URL in the database, but show converted URL to user id = await add_new_product( message.chat.id, product_name, url, price, display_url ) # Show different message based on whether affiliate tag was added if is_amazon_url(url) and 'tag=anrdcomm-21' in url: affiliate_msg = "\nšŸ”— Affiliate tag added for tracking" elif platform == "flipkart" and display_url != url: affiliate_msg = "\nšŸ”— Flipkart link converted for tracking" else: affiliate_msg = "" # Create inline keyboard with clickable buttons keyboard = InlineKeyboardMarkup([ [ InlineKeyboardButton("šŸ” View Details", callback_data=f"details_{id}"), InlineKeyboardButton("āŒ Stop Tracking", callback_data=f"stop_{id}") ] ]) await status.edit( f'āœ… Successfully tracking: "{product_name}"\n\n' f'šŸ’° Current Price: ₹{price}\n' f'šŸ›’ Platform: {platform.title()}{affiliate_msg}\n\n' f"šŸ“‹ Track My Products: /my_trackings\n" f"šŸ” Get details: /product_{id}", reply_markup=keyboard ) else: await status.edit("āŒ Failed to scrape product information! Please check if the URL is valid.") except Exception as e: print(f"Error in track_flipkart_url: {e}") try: await status.edit("āŒ An error occurred while processing your request.") except: await message.reply_text("āŒ An error occurred while processing your request.") @app.on_message(filters.command("product") & filters.private) async def track_product(_, message): try: # Handle both /product id and /product_id formats text_parts = message.text.split() if len(text_parts) == 2: # Format: /product id __, id = text_parts else: # Format: /product_id - extract ID from command command = message.text.split()[0] # Get the command part if command.startswith("/product_"): id = command.replace("/product_", "") else: await message.reply_text("āŒ Invalid product command format!") return status = await message.reply_text("Getting Product Info....") if id: product = await fetch_one_product(id) if product: product_name = product.get("product_name") product_url = product.get("display_url") or product.get("url") # Use display_url if available product_price = product.get("price") maximum_price = product.get("upper") minimum_price = product.get("lower") products_message = ( f"šŸ› **Product:** [{product_name}]({product_url})\n\n" f"šŸ’² **Current Price:** {product_price}\n" f"šŸ“‰ **Lowest Price:** {minimum_price}\n" f"šŸ“ˆ **Highest Price:** {maximum_price}\n" f"\nšŸ“‹ **Product ID:** `{id}`\n" f"āŒ **Stop tracking:** /stop_{id}" ) await status.edit(products_message, disable_web_page_preview=True) else: await status.edit("Product Not Found") else: await status.edit("Failed to fetch the product") except Exception as e: print(e) # Add a separate handler for product_ commands @app.on_message(filters.regex(r"^/product_[a-zA-Z0-9]+") & filters.private) async def track_product_underscore(_, message): try: # Extract ID from /product_id format command = message.text.strip() if command.startswith("/product_"): id = command.replace("/product_", "") status = await message.reply_text("Getting Product Info....") if id: product = await fetch_one_product(id) if product: product_name = product.get("product_name") product_url = product.get("display_url") or product.get("url") # Use display_url if available product_price = product.get("price") maximum_price = product.get("upper") minimum_price = product.get("lower") products_message = ( f"šŸ› **Product:** [{product_name}]({product_url})\n\n" f"šŸ’² **Current Price:** {product_price}\n" f"šŸ“‰ **Lowest Price:** {minimum_price}\n" f"šŸ“ˆ **Highest Price:** {maximum_price}\n" f"\nšŸ“‹ **Product ID:** `{id}`\n" f"āŒ **Stop tracking:** /stop_{id}" ) await status.edit(products_message, disable_web_page_preview=True) else: await status.edit("Product Not Found") else: await status.edit("Failed to fetch the product") else: await message.reply_text("āŒ Invalid product command format!") except Exception as e: print(f"Error in track_product_underscore: {e}") await message.reply_text("āŒ An error occurred while processing your request.") @app.on_message(filters.command("stop") & filters.private) async def delete_product(_, message): try: # Handle both /stop id and /stop_id formats text_parts = message.text.split() if len(text_parts) == 2: # Format: /stop id __, id = text_parts else: # Format: /stop_id - extract ID from command command = message.text.split()[0] # Get the command part if command.startswith("/stop_"): id = command.replace("/stop_", "") else: await message.reply_text("āŒ Invalid stop command format!") return status = await message.reply_text("Deleting Product....") chat_id = message.chat.id if id: is_deleted = await delete_one(id, chat_id) if is_deleted: await status.edit("Product Deleted from Your Tracking List") else: await status.edit("Failed to Delete the product") else: await status.edit("Failed to Delete the product") except Exception as e: print(e) # Add a separate handler for stop_ commands @app.on_message(filters.regex(r"^/stop_[a-zA-Z0-9]+") & filters.private) async def delete_product_underscore(_, message): try: # Extract ID from /stop_id format command = message.text.strip() if command.startswith("/stop_"): id = command.replace("/stop_", "") status = await message.reply_text("Deleting Product....") chat_id = message.chat.id if id: is_deleted = await delete_one(id, chat_id) if is_deleted: await status.edit("āœ… Product Deleted from Your Tracking List") else: await status.edit("āŒ Failed to Delete the product") else: await status.edit("āŒ Failed to Delete the product") else: await message.reply_text("āŒ Invalid stop command format!") except Exception as e: print(f"Error in delete_product_underscore: {e}") await message.reply_text("āŒ An error occurred while processing your request.") # Callback handlers for inline keyboard buttons @app.on_callback_query() async def handle_callback(_, callback_query): try: data = callback_query.data user_id = callback_query.from_user.id if data.startswith("details_"): # Handle "View Details" button product_id = data.replace("details_", "") product = await fetch_one_product(product_id) if product: product_name = product.get("product_name") product_url = product.get("display_url") or product.get("url") # Use display_url if available product_price = product.get("price") maximum_price = product.get("upper") minimum_price = product.get("lower") # Create inline keyboard for product details keyboard = InlineKeyboardMarkup([ [ InlineKeyboardButton("āŒ Stop Tracking", callback_data=f"stop_{product_id}"), InlineKeyboardButton("šŸ”„ Refresh Price", callback_data=f"refresh_{product_id}") ] ]) products_message = ( f"šŸ› **Product:** [{product_name}]({product_url})\n\n" f"šŸ’² **Current Price:** {product_price}\n" f"šŸ“‰ **Lowest Price:** {minimum_price}\n" f"šŸ“ˆ **Highest Price:** {maximum_price}\n" f"\nšŸ“‹ **Product ID:** `{product_id}`" ) await callback_query.edit_message_text( products_message, disable_web_page_preview=True, reply_markup=keyboard ) else: await callback_query.answer("āŒ Product not found!", show_alert=True) elif data.startswith("stop_"): # Handle "Stop Tracking" button product_id = data.replace("stop_", "") is_deleted = await delete_one(product_id, user_id) if is_deleted: await callback_query.edit_message_text("āœ… Product removed from your tracking list.") else: await callback_query.answer("āŒ Failed to remove product!", show_alert=True) elif data.startswith("refresh_"): # Handle "Refresh Price" button product_id = data.replace("refresh_", "") await callback_query.answer("šŸ”„ Refreshing price... (Feature coming soon!)", show_alert=True) except Exception as e: print(f"Callback error: {e}") await callback_query.answer("āŒ An error occurred!", show_alert=True) schedule.every().day.at("00:00").do(lambda: asyncio.run(check_prices(app))).tag( "daily_job" ) def run_schedule(): while True: schedule.run_pending() time.sleep(5) def main(): schedule_thread = threading.Thread(target=run_schedule) schedule_thread.start() app.run(print("Bot Running")) if __name__ == "__main__": main()