# Script to notify the best deals for your customized category of products in flipkart.

import time

from utils.flipkart_client import ProductGrabber
from utils.component.product_category import ProductCategory
from utils.constants import delay, config, useragent
from utils import db_client, message_builder, telegram_client, json_reader, flipkart_client, config_builder

db_client = db_client.SQliteDbClient(config.SQLITE_CONNECTION_STRING)
json_reader = json_reader.JsonReader(config.JSON_PATH)
telegram = telegram_client.TelegramClient()

telegram.sent_message("Flipkart Deal Notifier bot started.")

while True:
    # Read filter config from json file.
    filter_dict = json_reader.read_json_as_dict()
    
    # Insert all product categories into database.
    for category_name in filter_dict.keys():
        product_category = ProductCategory()
        product_category.category_name = category_name
        db_client.save_product_category(product_category)
    product_category_list = db_client.get_product_categories()
    
    # Get all products from db.
    product_list = db_client.get_products()

    # Capture the price of all products and update in db.
    for product_category in product_category_list:
        product_category_filter_url = config_builder.get_url_from_filter(filter_dict.get(product_category.category_name))
        page_count = flipkart_client.get_page_count(product_category_filter_url)

        for page_num in range(1, page_count + 1):
            product_category_page_url = flipkart_client.get_next_page_url(product_category_filter_url, page_num)
            product_links = flipkart_client.get_product_links(product_category_page_url, useragent.WINDOWS)

            for product_url in product_links:
                product_grabber = ProductGrabber(product_url)
                product = product_grabber.get_product_info()
                product.product_category_id = product_category.product_category_id

                if product.actual_price > 0:
                    product_in_db = next((prod for prod in product_list if prod.product_name == product.product_name), None)

                    if product_in_db is None:
                        db_client.save_product(product)
                    else:
                        if product.offer_price < product_in_db.offer_price:
                            db_client.update_product_price(product)
    
    # Get all products from db.
    product_list = db_client.get_products()

    # Sent notification in the telegram for unreported products.
    for product in product_list:
        if not product.is_reported:
            formatted_message = message_builder.get_formatted_message(product)
            telegram.sent_message(formatted_message)
            db_client.update_reported_product(product)
            time.sleep(delay.MESSAGE_INTERVAL_DELAY)

    time.sleep(delay.NEXT_RUN_DELAY)

main.py
Alpha-1729/FlipkartDealNotifier
import pyperclip
import urllib.parse

filter_url = input("Enter filter url: ")
category_name = input("Enter the category name: ")

# Remove all unwanted character from the url.
new_url = urllib.parse.unquote_plus(filter_url)
while new_url != filter_url:
    filter_url = new_url
    new_url = urllib.parse.unquote_plus(new_url)

filter_dic = {}
filters = filter_url.split("&p[]=")[1:]
for group in filters:
    category, value = group.split("=")
    if category in filter_dic.keys():
        filter_dic[category] += ", " + '"' + value + '"'
    else:
        filter_dic[category] = '"' + value + '"'

# Generating the json content.
content = ""
for key, value in filter_dic.items():
    content += "\t\t" + '"&p[]=' + key + '=" : [' + value + "],"

output = """"{}":{{
        "search?q=" : "{}",
        "&as-show=" : "on",
        "&as=" : "off",
        "&sort=" : "price_asc",
        {}
        \t}}""".format(
    category_name.title(), category_name.lower(), content[:-1]
)

pyperclip.copy(output)
print("Json entry has been copied to clipboard")

tools/url_to_json_converter.py
Alpha-1729/FlipkartDealNotifier
import string


class Product:
    product_id: int
    product_name: string
    product_url: string
    product_category_id: int
    actual_price: float
    offer_price: float
    is_reported: bool

    def __init__(
            self,
            product_id=None,
            product_name=None,
            product_url=None,
            product_category_id=None,
            actual_price=None,
            offer_price=None,
            is_reported=None,
    ):
        self.product_id = product_id
        self.product_name = product_name
        self.product_url = product_url
        self.product_category_id = product_category_id
        self.actual_price = actual_price
        self.offer_price = offer_price
        self.is_reported = is_reported

utils/component/product.py
Alpha-1729/FlipkartDealNotifier
import string


class ProductCategory:
    product_category_id: int
    category_name: string

    def __init__(
            self,
            product_category_id=None,
            category_name=None
    ):
        self.product_category_id = product_category_id
        self.category_name = category_name

utils/component/product_category.py
Alpha-1729/FlipkartDealNotifier
def get_url_from_filter(product_filter):
    url = r"https://www.flipkart.com/"

    for key, value in product_filter.items():
        if type(value) == type(list()):
            for item in value:
                url += key + item
        else:
            url += key + value
    return url

utils/config_builder.py
Alpha-1729/FlipkartDealNotifier
SQLITE_CONNECTION_STRING = r"C:\Users\-\Desktop\FlipkartDealNotifier\db\products.db"
JSON_PATH = r"C:\Users\-\Desktop\FlipkartDealNotifier\config\config.json"

utils/constants/config.py
Alpha-1729/FlipkartDealNotifier
# All values are in seconds.
MESSAGE_INTERVAL_DELAY = 1
NEXT_RUN_DELAY = 60 * 60 * 1

utils/constants/delay.py
Alpha-1729/FlipkartDealNotifier
SELECT_PRODUCTS = "SELECT * FROM Product;"

SELECT_PRODUCT_CATEGORIES = "SELECT * FROM ProductCategory;"

SELECT_UNREPORTED_PRODUCTS = "SELECT * FROM Product WHERE is_reported = 0;"

INSERT_PRODUCT = "INSERT INTO Product(product_name, product_url, product_category_id, actual_price, offer_price, is_reported) VALUES (:product_name, :product_url, :product_category_id,  :actual_price, :offer_price, :is_reported);"

INSERT_PRODUCT_CATEGORY = "INSERT OR IGNORE INTO ProductCategory(category_name) VALUES (:category_name);"

UPDATE_REPORTED_PRODUCT = "UPDATE Product SET is_reported = 1 WHERE product_name = :product_name;"

UPDATE_PRODUCT_PRICE = "UPDATE Product SET actual_price = :actual_price, offer_price = :offer_price, is_reported = 0 WHERE product_name = :product_name;"

utils/constants/queries.py
Alpha-1729/FlipkartDealNotifier
ANDROID = {
    "User-Agent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36"
}

WINDOWS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0"
}

utils/constants/useragent.py
Alpha-1729/FlipkartDealNotifier
import sqlite3

from utils.constants import queries
from utils.component.product import Product
from utils.component.product_category import ProductCategory


class SQliteDbClient:
    def __init__(self, connection_string):
        self.connection_string = connection_string

    def save_product(self, product: Product):
        conn = sqlite3.connect(self.connection_string)
        cursor = conn.cursor()

        cursor.execute(queries.INSERT_PRODUCT, product.__dict__)
        conn.commit()

        cursor.close()

    def save_product_category(self, product_category: ProductCategory):
        conn = sqlite3.connect(self.connection_string)
        cursor = conn.cursor()

        cursor.execute(queries.INSERT_PRODUCT_CATEGORY, product_category.__dict__)
        conn.commit()

        cursor.close()

    def get_products(self):
        conn = sqlite3.connect(self.connection_string)
        cursor = conn.cursor()

        products = cursor.execute(queries.SELECT_PRODUCTS).fetchall()

        cursor.close()

        return [Product(*row) for row in products]

    def get_product_categories(self):
        conn = sqlite3.connect(self.connection_string)
        cursor = conn.cursor()

        product_categories = cursor.execute(queries.SELECT_PRODUCT_CATEGORIES).fetchall()

        cursor.close()

        return [ProductCategory(*row) for row in product_categories]

    def get_unreported_products(self):
        conn = sqlite3.connect(self.connection_string)
        cursor = conn.cursor()

        products = cursor.execute(queries.SELECT_UNREPORTED_PRODUCTS).fetchall()

        cursor.close()

        return [Product(*row) for row in products]

    def update_product_price(self, product: Product):
        conn = sqlite3.connect(self.connection_string)
        cursor = conn.cursor()

        cursor.execute(queries.UPDATE_PRODUCT_PRICE, product.__dict__)
        conn.commit()

        cursor.close()

    def update_reported_product(self, product: Product):
        conn = sqlite3.connect(self.connection_string)
        cursor = conn.cursor()

        cursor.execute(queries.UPDATE_REPORTED_PRODUCT, product.__dict__)
        conn.commit()

        cursor.close()

utils/db_client.py
Alpha-1729/FlipkartDealNotifier
import re
import requests
from bs4 import BeautifulSoup

from utils.constants import useragent
from utils.component.product import Product
from utils.thread_client import CustomThread


class ProductGrabber:
    def __init__(self, url):
        self.url = url

    def get_product_info(self):
        custom_thread_windows = CustomThread(target=get_webpage, args=(self.url, useragent.WINDOWS))
        custom_thread_android = CustomThread(target=get_webpage, args=(self.url, useragent.ANDROID))

        custom_thread_windows.start()
        custom_thread_android.start()

        product_page_windows = custom_thread_windows.join()
        product_page_android = custom_thread_android.join()

        product = Product()
        product.product_url = self.url
        product.product_name = get_product_name(product_page_windows)
        product.actual_price = get_product_price(product_page_windows)
        product.offer_price = get_offer_price(product_page_android)

        if product.offer_price is None:
            product.offer_price = product.actual_price

        product.is_reported = False

        return product


def get_offer_price(webpage_android):
    price = get_offer_price_with_regex(webpage_android)
    if price is None:
        price = get_offer_price_with_class(webpage_android)
        return price
    return price


def get_page_count(url):
    try:
        soup = get_webpage(url, useragent.WINDOWS)

        page_info = soup.find("div", attrs={"class": "_2MImiq"})
        last_index = page_info.span.text.split(" of ")[-1]
        return int(last_index)
    except AttributeError:
        return 1


def get_next_page_url(url, current_page):
    return url + "&page=" + str(current_page)


def get_webpage(url, user_agent=None):
    page = requests.get(url, headers=user_agent)
    soup = BeautifulSoup(page.text, "html.parser")
    return soup


def get_product_price(webpage_windows):
    try:
        price = webpage_windows.find("div", {"class": "_30jeq3 _16Jk6d"}).get_text().strip()[1:].replace(',', '')
        if price:
            return int(price)
        return -1
    except:
        return -1


def get_product_name(webpage_windows):
    product_name = (webpage_windows.find("span", {"class": "B_NuCI"}).get_text().strip())
    return product_name


def get_offer_price_with_class(webpage_android):
    offer_price_classes = (
        "css-901oao r-1wv73ep",
        "css-901oao r-1wv73ep r-1vgyyaa r-1rsjblm",
        "css-901oao r-1wv73ep r-1vgyyaa r-ubezar r-1rsjblm",
        "css-901oao r-1wv73ep r-1w427b9 r-1vgyyaa r-1rsjblm",
    )

    for class_name in offer_price_classes:
        offer_price_list = webpage_android.findAll("div", {"class": class_name})
        if offer_price_list is not None:
            prices = [match.get_text() for match in offer_price_list if match.get_text() is not None]
            prices = [price.strip()[1:].replace(',', '') for price in prices]
            if prices:
                return prices[0]
            return None
    return None


def get_offer_price_with_regex(webpage_android):
    matches = re.findall(
        r'text":"₹[0-9]+,[0-9]+[,]*[0-9]*","textColor":', str(webpage_android)
    )
    prices = [match[8:-14].replace(",", "") for match in matches]
    prices = [int(price) for price in prices if price.isnumeric()]
    if prices:
        return prices[0]
    return None


def get_product_links(url, user_agent=None):
    product_category_sub_page = get_webpage(url, user_agent)
    product_links = re.findall("""http://dl.flipkart.com/dl/.*?(?=")""", str(product_category_sub_page))
    product_links = [links for links in product_links if "pid=" in links]
    return product_links

utils/flipkart_client.py
Alpha-1729/FlipkartDealNotifier
import json


class JsonReader:
    def __init__(self, json_path):
        self.json_path = json_path

    def read_json_as_dict(self):
        with open(self.json_path, "r") as content:
            return json.load(content)

utils/json_reader.py
Alpha-1729/FlipkartDealNotifier
from utils.component.product import Product


def get_formatted_message(product: Product):
    message = "ProductName: {}\n\nActualPrice: {}\n\nOfferPrice: {}\n\nProductLink: {}\n\n".format(
        product.product_name,
        product.actual_price,
        product.offer_price,
        product.product_url,
    )
    return message

utils/message_builder.py
Alpha-1729/FlipkartDealNotifier
import os
import requests
import urllib.parse


class TelegramClient:
    def __init__(self):
        self.telegram_bot_key = os.environ.get("TELEGRAM_BOT_KEY")
        self.telegram_chat_id = os.environ.get("TELEGRAM_CHAT_ID")

        if self.telegram_bot_key is None or self.telegram_chat_id is None:
            raise Exception("Please configure TELEGRAM_BOT_KEY and TELEGRAM_CHAT_ID in environment variables.")

        self.telegram_post_url = r"https://api.telegram.org/bot{}/sendMessage?chat_id={}&text=".format(
            self.telegram_bot_key, self.telegram_chat_id)

    def sent_message(self, message):
        url = "{}{}".format(self.telegram_post_url, urllib.parse.quote_plus(message))
        requests.post(url)

utils/telegram_client.py
Alpha-1729/FlipkartDealNotifier
from threading import Thread


class CustomThread(Thread):
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs={}, Verbose=None):
        Thread.__init__(self, group, target, name, args, kwargs)
        self._return = None

    def run(self):
        if self._target is not None:
            self._return = self._target(*self._args, **self._kwargs)

    def join(self, *args):
        Thread.join(self, *args)
        return self._return

utils/thread_client.py
Alpha-1729/FlipkartDealNotifier