diff --git a/.DS_Store b/.DS_Store
index 7246d77..3ebfd57 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.github/.DS_Store b/.github/.DS_Store
new file mode 100644
index 0000000..0d33471
Binary files /dev/null and b/.github/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index c6176a5..82d3769 100644
--- a/.gitignore
+++ b/.gitignore
@@ -133,6 +133,8 @@ dmypy.json
# Pyre type checker
.pyre/
-shopsync-se-firebase-adminsdk-nkzuw-e871ea65d4.json
-shopsync-se-firebase-adminsdk-nkzuw-5c1cd78bc9.json
-shopsync-se-firebase-adminsdk-nkzuw-ca6838f54f.json
\ No newline at end of file
+# shopsync-se-firebase-adminsdk-nkzuw-e871ea65d4.json
+# shopsync-se-firebase-adminsdk-nkzuw-5c1cd78bc9.json
+# shopsync-se-firebase-adminsdk-nkzuw-ca6838f54f.json
+# src/frontend/shopsync-9ecdc-firebase-adminsdk-60nyc-7e5a173fe8.json
+shopsync-9ecdc-firebase-adminsdk-60nyc-7e5a173fe8.json
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index cc9f0a3..f140dea 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -91,6 +91,7 @@ sniffio==1.3.1
soupsieve==2.6
starlette==0.38.6
streamlit==1.38.0
+streamlit-cookies-controller==0.0.4
streamlit-option-menu==0.4.0
tenacity==8.5.0
toml==0.10.2
diff --git a/src/.DS_Store b/src/.DS_Store
index 403f523..541aa35 100644
Binary files a/src/.DS_Store and b/src/.DS_Store differ
diff --git a/src/configs.py b/src/configs.py
index 585928e..438b7d8 100644
--- a/src/configs.py
+++ b/src/configs.py
@@ -59,6 +59,8 @@
'title_indicator': 'h4.sku-header a',
'price_indicator': 'div.priceView-customer-price span',
'link_indicator': 'a.image-link',
+ 'image_indicator': 'image.primary-image',
+ 'review_indicator': 'span.ugc-c-review-average'
}
diff --git a/src/configs_mt.py b/src/configs_mt.py
index 6d620a4..d89592e 100644
--- a/src/configs_mt.py
+++ b/src/configs_mt.py
@@ -23,34 +23,54 @@
'data-item-id': True
},
'title_indicator': 'span.lh-title',
- 'price_indicator': 'div.lh-copy',
- 'link_indicator': 'a'
+ 'price_indicator': 'div[data-automation-id="product-price"] span.w_iUH7',
+ 'link_indicator': 'a',
+ 'image_indicator': 'img.absolute.top-0.left-0',
+ 'review_indicator': 'span.stars-container'
}
-AMAZON = {
- 'site': 'amazon',
- 'url': 'https://www.amazon.com/s?k=',
- 'item_component': 'div',
- 'item_indicator': {
- 'data-component-type': 's-search-result'
- },
- 'title_indicator': 'h2 a span',
- 'price_indicator': 'span.a-price span',
- 'link_indicator': 'h2 a.a-link-normal'
-}
+# AMAZON = {
+# 'site': 'amazon',
+# 'url': 'https://www.amazon.com/s?k=',
+# 'item_component': 'div',
+# 'item_indicator': {
+# 'data-component-type': 's-search-result'
+# },
+# 'title_indicator': 'h2 a span',
+# 'price_indicator': 'span.a-price span',
+# 'link_indicator': 'h2 a.a-link-normal',
+# 'image_indicator': 'img.s-image',
+# 'review_indicator': 'span.a-declarative a i span'
+# }
COSTCO = {
'site': 'costco',
'url': 'https://www.costco.com/CatalogSearch?dept=All&keyword=',
'item_component': 'div',
'item_indicator': {
- 'class': 'product-tile-set'
+ 'data-testid': 'Grid'
},
- 'title_indicator': 'span a',
- 'price_indicator': 'div.price',
- 'link_indicator': 'span.description a',
+ 'title_indicator': 'div[data-testid^="Text_ProductTile_"]', # Extract the title from the span
+ 'price_indicator': 'div.MuiTypography-root', # Extract the price element
+ 'link_indicator': 'a', # Anchor element for the product link
+ 'image_indicator': 'img', # Use `img` tag for the product image
+ 'review_indicator': 'div.product-rating' # Review container (verify its presence)
}
+# TARGET = {
+# 'site': 'target',
+# 'url': 'https://www.target.com/s?searchTerm=',
+# 'item_component': 'div',
+# 'item_indicator': {
+# 'data-test': '@web/ProductCard/ProductCardVariantDefault'
+# },
+# 'title_indicator': 'a[data-test="product-title"]',
+# 'price_indicator': 'span[data-test="product-price"]',
+# 'link_indicator': 'a[data-test="product-title"]',
+# 'image_indicator': 'img[data-test="product-image"]',
+# 'review_indicator': 'div[data-test="average-rating"]'
+# }
+
BESTBUY = {
'site': 'bestbuy',
'url': 'https://www.bestbuy.com/site/searchpage.jsp?st=',
@@ -61,6 +81,8 @@
'title_indicator': 'h4.sku-title a',
'price_indicator': 'div.priceView-customer-price span',
'link_indicator': 'a.image-link',
+ 'image_indicator': 'img.product-image',
+ 'review_indicator': 'div.c-ratings-reviews p'
}
@@ -85,43 +107,9 @@ def run(self):
List of items from the dict
"""
- # api_url = 'https://redsky.target.com/redsky_aggregations/v1/web/plp_search_v1'
-
- # page = '/s/' + self.query
- # params = {
- # 'key': '5938CFDFD3FB4A7DB7C060583C86663C',
- # 'channel': 'WEB',
- # 'count': '24',
- # 'default_purchasability_filter': 'false',
- # 'include_sponsored': 'true',
- # 'keyword': self.query,
- # 'offset': '0',
- # 'page': page,
- # 'platform': 'desktop',
- # 'pricing_store_id': '3991',
- # 'useragent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0',
- # 'visitor_id': 'AAA',
- # }
-
- # data = requests.get(api_url, params=params).json()
- # items = []
- # if 'search' in data['data']:
- # for p in data['data']['search']['products']:
- # item = {
- # 'timestamp': datetime.now().strftime("%d/%m/%Y %H:%M:%S"),
- # 'title': html.unescape(p['item']['product_description']['title']),
- # 'price': '$' + str(p['price']['current_retail']),
- # 'website': 'target',
- # #'link': shorten_url(p['item']['enrichment']['buy_url'])
- # 'link': p['item']['enrichment']['buy_url']
- # }
- # items.append(item)
-
- # self.result = items
- # set up the request parameters
params = {
- 'api_key': '5938CFDFD3FB4A7DB7C060583C86663C',
- 'search_term': 'Iphone',
+ 'api_key': 'A6D50D2FA74944AEB91D4B18CBFDE4B0',
+ 'search_term': self.query,
'type': 'search',
'sort_by': 'best_match'
}
@@ -178,7 +166,7 @@ def run(self):
self.result = []
data = response.dict()
-
+ print(data)
items = []
for p in data['searchResult']['item']:
item = {
@@ -187,11 +175,77 @@ def run(self):
'price': '$' + p['sellingStatus']['currentPrice']['value'],
'website': 'ebay',
#'link': shorten_url(p['viewItemURL'])
- 'link': p['viewItemURL']
+ 'link': p['viewItemURL'],
+ 'image_url': p['galleryURL'] if 'galleryURL' in p else 'https://via.placeholder.com/150',
+ 'review': p['sellerInfo']['positiveFeedbackPercent'] + '%' if 'sellerInfo' in p and 'positiveFeedbackPercent' in p['sellerInfo'] else 'No Reviews'
}
items.append(item)
self.result = items
-CONFIGS = [WALMART, AMAZON, COSTCO, BESTBUY]
+
+
+class scrape_amazon(Thread):
+ def __init__(self, query):
+ self.result = []
+ self.query = query
+ super(scrape_amazon, self).__init__()
+
+ def run(self):
+ """Scrape Amazon product data using Rainforest API
+
+ Parameters
+ ----------
+ query: str
+ Item to look for in the API
+
+ Returns
+ ----------
+ items: list
+ List of items from the API response
+ """
+ API_KEY = '34C1B2689C074F3BB120B22625C22F72'
+ BASE_URL = 'https://api.rainforestapi.com/request'
+
+ params = {
+ 'api_key': API_KEY,
+ 'type': 'search',
+ 'amazon_domain': 'amazon.com',
+ 'search_term': self.query,
+ }
+
+ try:
+ # Send request to Rainforest API
+ response = requests.get(BASE_URL, params=params)
+ data = response.json()
+
+ # Extract items from response
+ items = []
+ if "search_results" in data:
+ for product in data["search_results"]:
+ try:
+ item = {
+ 'timestamp': datetime.now().strftime("%d/%m/%Y %H:%M:%S"),
+ 'title': product['title'] if 'title' in product else 'No Title',
+ 'price': f"${product['price']['value']}" if 'price' in product and 'value' in product['price'] else 'No Price',
+ 'currency': product['price']['currency'] if 'price' in product and 'currency' in product['price'] else 'USD',
+ 'website': 'amazon',
+ 'link': product['link'] if 'link' in product else 'No Link',
+ 'review': f"{product['rating']} stars" if 'rating' in product else 'No Rating',
+ 'image_url': product['image'] if 'image' in product else 'https://via.placeholder.com/150'
+
+ }
+ items.append(item)
+ except Exception as e:
+ print(f"Error processing product: {e}")
+ continue
+
+ self.result = items
+
+ except Exception as e:
+ print(f"Error fetching data from Amazon API: {e}")
+ self.result = []
+
+
+CONFIGS = [WALMART, COSTCO, BESTBUY]
diff --git a/src/formattr.py b/src/formattr.py
index c165bd6..76de949 100644
--- a/src/formattr.py
+++ b/src/formattr.py
@@ -15,7 +15,7 @@
"""
-def formatResult(website, titles, prices, links):
+def formatResult(website, titles, prices, links, image_url=None, review=None):
"""
The formatResult function takes the scraped HTML as input, and extracts the
necessary values from the HTML code. Ex. extracting a price '$19.99' from
@@ -34,8 +34,10 @@ def formatResult(website, titles, prices, links):
"price": price,
"link": f'www.{website}.com{link}',
"website": website,
+ "image_url": image_url or "https://via.placeholder.com/150",
+ "review": review or "No Reviews"
}
- print(product['title'])
+ #print(product['title'])
if website=='walmart':
if link[0:4]=='http':
product['link']=f'{link}'
diff --git a/src/frontend/.DS_Store b/src/frontend/.DS_Store
new file mode 100644
index 0000000..2c4eff5
Binary files /dev/null and b/src/frontend/.DS_Store differ
diff --git a/src/frontend/account.py b/src/frontend/account.py
index acb488b..e55c1b0 100644
--- a/src/frontend/account.py
+++ b/src/frontend/account.py
@@ -11,6 +11,10 @@
from PIL import Image
from PIL import Image
from dotenv import load_dotenv
+import json
+import slash_user_interface as slash_user_interface
+from streamlit_cookies_controller import CookieController
+import time
# Load environment variables from .env file
load_dotenv()
@@ -27,8 +31,8 @@ def initialize_firebase(mock=False):
if not firebase_admin._apps:
firebase_admin.initialize_app()
return True
+ json_path = os.path.join(os.path.dirname(__file__), 'shopsync-9ecdc-firebase-adminsdk-60nyc-a335ead1ea.json')
- json_path = os.path.join(os.path.dirname(__file__), 'shopsync-se-firebase-adminsdk-nkzuw-e871ea65d4.json')
try:
# Path to Firebase service account key
cred = credentials.Certificate(json_path)
@@ -209,6 +213,25 @@ def app():
# If login successful, update session state
st.session_state.logged_in = True
st.session_state.user_email = email
+
+ with open('tokens.json') as openfile:
+ json_object = json.load(openfile)
+
+ controller = CookieController()
+ token = controller.get("csrftoken")
+
+ tokenData = {
+ "email": email,
+ "expiration": time.time() + 70000
+ }
+
+ json_object["tokens"][token] = tokenData
+
+ json_output = json.dumps(json_object, indent=4)
+
+ with open('tokens.json', 'w') as openfile:
+ openfile.write(json_output)
+
st.success('Logged in successfully!')
st.rerun() # Rerun the app to reflect login state
@@ -299,6 +322,19 @@ def signup(username, email, password):
raise ValueError("An unexpected error occurred.")
def logout():
+ # Delete token on logout
+ with open('tokens.json', 'r') as openfile:
+ json_object = json.load(openfile)
+ controller = CookieController()
+ token = controller.get("csrftoken")
+
+ del json_object["tokens"][token]
+
+ json_output = json.dumps(json_object, indent=4)
+
+ with open('tokens.json', 'w') as openfile:
+ openfile.write(json_output)
+
# Clear session state on logout
st.session_state.logged_in = False
st.session_state.user_email = None
diff --git a/src/frontend/app.py b/src/frontend/app.py
index 7d62a4f..e9bb35f 100644
--- a/src/frontend/app.py
+++ b/src/frontend/app.py
@@ -5,6 +5,11 @@
import slash_user_interface as slash_user_interface
import favourites
import logout # Import the logout module
+import json
+import history
+import slash_user_interface as slash_user_interface
+import time
+from streamlit_cookies_controller import CookieController
import warnings
warnings.filterwarnings("ignore", category=UserWarning, module='streamlit')
@@ -19,14 +24,40 @@ def add_app(self, title, function):
})
def run(self):
+ # See if already logged in
+ with open('tokens.json', 'r') as openfile:
+ json_object = json.load(openfile)
+ controller = CookieController()
+ token = controller.get("csrftoken")
+
+ if token not in json_object["tokens"]:
+ if 'logged_in' not in st.session_state:
+ st.session_state.logged_in = False
+ if 'user_email' not in st.session_state:
+ st.session_state.user_email = None
+ else:
+ if time.time() > json_object["tokens"][token]["expiration"]:
+ del json_object["tokens"][token]
+
+ json_output = json.dumps(json_object, indent=4)
+
+ with open('tokens.json', 'w') as openfile:
+ openfile.write(json_output)
+
+ st.session_state.logged_in = False
+ st.session_state.user_email = None
+ else:
+ st.session_state.logged_in = True
+ st.session_state.user_email = json_object["tokens"][token]["email"]
+
# Determine the default selected app based on login status
default_index = 0 if not st.session_state.get('logged_in') else 1
with st.sidebar:
app = option_menu(
menu_title='ShopSync',
- options=['Account', 'Home', 'Favourites', 'Logout'],
- icons=['person-circle', 'house-fill', 'star-fill', 'box-arrow-right'],
+ options=['Account', 'Home', 'Favourites', 'History', 'Logout'],
+ icons=['person-circle', 'house-fill', 'star-fill', 'box-arrow-right', 'box-arrow-right'],
menu_icon='shop',
default_index=default_index,
styles={
@@ -52,7 +83,13 @@ def run(self):
favourites.app()
else:
st.warning("You need to log in to access the favourites page.")
-
+
+ elif app == "History":
+ if st.session_state.get('logged_in'):
+ history.app()
+ else:
+ st.warning("You need to log in to access the history page.")
+
elif app == "Logout":
logout.app() # Call the logout function
@@ -62,4 +99,5 @@ def run(self):
app.add_app("Account", account.app)
app.add_app("Home", slash_user_interface.app)
app.add_app("Favourites", favourites.app)
+ app.add_app("History", history.app)
app.run()
\ No newline at end of file
diff --git a/src/frontend/favourites.py b/src/frontend/favourites.py
index 4559a87..1a6642c 100644
--- a/src/frontend/favourites.py
+++ b/src/frontend/favourites.py
@@ -9,14 +9,12 @@ def fetch_title():
def initialize_firebase(mock=False):
if mock:
- # Mock initialization for testing purposes
if not firebase_admin._apps:
firebase_admin.initialize_app()
return True
- json_path = os.path.join(os.path.dirname(__file__), 'shopsync-se-firebase-adminsdk-nkzuw-e871ea65d4.json')
+ json_path = os.path.join(os.path.dirname(__file__), 'shopsync-9ecdc-firebase-adminsdk-60nyc-7e5a173fe8.json')
try:
- # Path to Firebase service account key
cred = credentials.Certificate(json_path)
firebase_admin.initialize_app(cred)
return True
@@ -24,19 +22,15 @@ def initialize_firebase(mock=False):
print(f"Error initializing Firebase: {e}")
raise e
-# db = firestore.client()
def app(firestore_client=None):
- # Allow mock initialization
if not firebase_admin._apps:
initialize_firebase(mock=False)
-
- # If firestore_client is None, initialize it
+
if firestore_client is None:
firestore_client = firestore.client()
st.title("Favorites")
- # Fetch the user ID (UID) from Firebase Authentication
user_email = st.session_state.user_email # Ensure this is set
user = auth.get_user_by_email(user_email) # Get the user by email
uid = user.uid
@@ -48,21 +42,37 @@ def app(firestore_client=None):
if user_fav_doc.exists:
user_fav_data = user_fav_doc.to_dict()
- # Create a DataFrame from the user's favorites
+ # Convert user's favorites into a DataFrame
favorites_df = pd.DataFrame({
+ "Image_URL": user_fav_data["Image_URL"],
"Description": user_fav_data["Description"],
- "Link": user_fav_data["Link"],
"Price": user_fav_data["Price"],
"Product": user_fav_data["Product"],
"Website": user_fav_data["Website"],
})
# Display the favorites DataFrame
- st.dataframe(favorites_df.style, column_config={
- "Link": st.column_config.LinkColumn("URL to Website"),
- "Button": st.column_config.LinkColumn("Add to fav"),
- })
+ print("there")
+ for index, row in favorites_df.iterrows():
+ st.write(f"### {row['Product']}")
+ st.write(f"**Description:** {row['Description']}")
+ st.write(f"**Price:** {row['Price']}")
+ st.write(f"**Website:** {row['Website']}")
+ st.markdown(f"[View Product]({row['Link']})", unsafe_allow_html=True)
+
+ # Add a "Remove from Favorites" button
+ print("here")
+ if st.button(f"Remove {row['Product']}", key=f"remove_{index}"):
+ # Remove the product from Firestore
+ updated_favorites = {
+ "Description": [d for i, d in enumerate(user_fav_data["Description"]) if i != index],
+ "Link": [l for i, l in enumerate(user_fav_data["Link"]) if i != index],
+ "Price": [p for i, p in enumerate(user_fav_data["Price"]) if i != index],
+ "Product": [p for i, p in enumerate(user_fav_data["Product"]) if i != index],
+ "Website": [w for i, w in enumerate(user_fav_data["Website"]) if i != index],
+ }
+ user_fav_ref.set(updated_favorites)
+ st.success(f"{row['Product']} removed from favorites.")
+ #st.experimental_rerun() # Refresh the page after removal
else:
st.write("You have no favorites yet.")
-
-# In your main app file, call favourites.app() to use this
diff --git a/src/frontend/firebase.py b/src/frontend/firebase.py
index fa3dc03..828c98e 100644
--- a/src/frontend/firebase.py
+++ b/src/frontend/firebase.py
@@ -4,7 +4,7 @@
def initialize_firebase():
# Check if Firebase has already been initialized
if not firebase_admin._apps:
- cred = credentials.Certificate('shopsync-se-firebase-adminsdk-nkzuw-e871ea65d4.json')
+ cred = credentials.Certificate('shopsync-9ecdc-firebase-adminsdk-60nyc-7e5a173fe8.json')
# cred = credentials.Certificate('shopsync-se-firebase-adminsdk-nkzuw-ca6838f54f.json')
firebase_admin.initialize_app(cred)
\ No newline at end of file
diff --git a/src/frontend/history.py b/src/frontend/history.py
new file mode 100644
index 0000000..f59f99e
--- /dev/null
+++ b/src/frontend/history.py
@@ -0,0 +1,71 @@
+import streamlit as st
+import firebase_admin
+from firebase_admin import credentials, firestore, auth
+import pandas as pd
+import os
+
+def fetch_title():
+ return "History"
+
+def initialize_firebase(mock=False):
+ if mock:
+ # Mock initialization for testing purposes
+ if not firebase_admin._apps:
+ firebase_admin.initialize_app()
+ return True
+
+ json_path = os.path.join(os.path.dirname(__file__), 'shopsync-se-firebase-adminsdk-nkzuw-e871ea65d4.json')
+ try:
+ # Path to Firebase service account key
+ cred = credentials.Certificate(json_path)
+ firebase_admin.initialize_app(cred)
+ return True
+ except Exception as e:
+ print(f"Error initializing Firebase: {e}")
+ raise e
+
+# db = firestore.client()
+def app(firestore_client=None):
+ # Allow mock initialization
+ if not firebase_admin._apps:
+ initialize_firebase(mock=False)
+
+ # If firestore_client is None, initialize it
+ if firestore_client is None:
+ firestore_client = firestore.client()
+
+ st.title("History")
+
+ # Fetch the user ID (UID) from Firebase Authentication
+ user_email = st.session_state.user_email # Ensure this is set
+ user = auth.get_user_by_email(user_email) # Get the user by email
+ uid = user.uid
+
+ # Reference to the user's document in the "history" collection
+ user_his_ref = firestore_client.collection("history").document(uid)
+ user_his_doc = user_his_ref.get()
+
+ if user_his_doc.exists:
+ user_his_data = user_his_doc.to_dict()
+
+ timestamps = list()
+ for stamp in user_his_data["Timestamp"]:
+ temp = pd.Timestamp(stamp, unit='s', tz='US/Eastern')
+ # temp.floor(freq='h')
+ timestamps.append(temp.floor(freq='s'))
+
+ # Create a DataFrame from the user's history
+ history_df = pd.DataFrame({
+ "Search": user_his_data["Search"],
+ "Timestamp": timestamps
+ })
+
+ # Display the history DataFrame
+ st.dataframe(history_df.style, column_config={
+ "Link": st.column_config.LinkColumn("URL to Website"),
+ "Button": st.column_config.LinkColumn("Add to history"),
+ })
+ else:
+ st.write("You have no history yet.")
+
+# In your main app file, call history.app() to use this
diff --git a/src/frontend/logger.txt b/src/frontend/logger.txt
index 2cef09f..d239385 100644
--- a/src/frontend/logger.txt
+++ b/src/frontend/logger.txt
@@ -49,3 +49,162 @@ amazon query:iphone
amazon query:iphone
amazon query:iphone
amazon query:ginger
+amazon query:watch
+amazon query:bag
+amazon query:shirt
+amazon query:bag
+amazon query:iphone
+amazon query:dunks
+amazon query:banana
+amazon query:t shirt
+amazon query:apple
+amazon query:banana
+amazon query:apple
+amazon query:water
+amazon query:glass
+amazon query:popcorn
+amazon query:plate
+amazon query:banana
+amazon query:wine
+amazon query:water
+amazon query:mango
+amazon query:bananna
+amazon query:coffee
+amazon query:cocoa
+amazon query:apple
+amazon query:shoes
+amazon query:Icecream
+amazon query:Icecream
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Apple
+amazon query:Apple
+amazon query:Banana
+amazon query:Milk
+amazon query:Milk
+amazon query:Apple
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:milk
+amazon query:Milk
+amazon query:Apple
+amazon query:Apple
+amazon query:Apple
+amazon query:Airpod
+amazon query:Airpod
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Apple
+amazon query:Apple
+amazon query:Milk
+amazon query:Milk
+amazon query:Iphone
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Apple
+amazon query:Apple
+amazon query:Apple
+amazon query:Apple
+amazon query:Milk
+amazon query:Milk
+amazon query:Phone
+amazon query:Apple
+amazon query:Apples
+amazon query:Apples
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:Milk
+amazon query:milk
+amazon query:milk
+amazon query:milk
+amazon query:milk
+amazon query:milk
+amazon query:milk
+amazon query:milk
+amazon query:Apple
+amazon query:Apples
+amazon query:Apples
+amazon query:Apples
+amazon query:Apples
+amazon query:milk
+amazon query:milk
+amazon query:Apples
+amazon query:Apples
+amazon query:Apples
diff --git a/src/frontend/shopsync-9ecdc-firebase-adminsdk-60nyc-05d8e88f22.json b/src/frontend/shopsync-9ecdc-firebase-adminsdk-60nyc-05d8e88f22.json
new file mode 100644
index 0000000..d563401
--- /dev/null
+++ b/src/frontend/shopsync-9ecdc-firebase-adminsdk-60nyc-05d8e88f22.json
@@ -0,0 +1,13 @@
+{
+ "type": "service_account",
+ "project_id": "shopsync-9ecdc",
+ "private_key_id": "05d8e88f22ac83c91b58b295a781a55ed4c4990e",
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5R1i2UbzZ77zD\nOhrxbWA1VMtX2sZuplbpyZGHevDVBw3zeZBtQsdc8xTy/ooF8Z7WIejWH1nVGKYe\nmEMvl9g2B8mYrd1UNdEEYL0X/GRl6BVsog5n0TGyV4wwiUkZmFM9RClr0R4hVxhs\nCuAyQUpcr2Srqmd3SNllsFkWGpwvOKsKTSeCG/n2Ott7FqxyXR4OnHTrEjCO2brb\nGU5/J7d92XFUc/m9GGWALSyxel6qQ5moAinPX/oeCkiV8FVsUQsStUDkl7HjzsWg\n7eneBYiqzSacILCmqCqxdCe32M5MHWdi/yzSNSUW2xY2Bv81XOETOxmUBP3RAQeO\ng8ELG1NxAgMBAAECggEAGLpCR2hL9soy5zCvZ96xIxeuC+D8RXLt0UXq+6nsz9b2\nhbLeaQwAj5HhueHpieGK0V61NIlP8/YriunHYxK2SH/BksmhlcZDnydKFWlvUip8\nsYLvaUECNksjlVa9QC0+7sOqGORP9nh+n0IpqeC3i/yHSGH1wnXL40Z7R/fv4F1K\nz1UwTCwAFRApSRKylAX7ZM9CbDu3r4ZQD5g98CwDhRgvC+FOVPT5L0ogC5HFs/ZO\nLkWqQpqw9Oo5RNhlkjqVEqPNSKytb7esYp1zsA7jfWyspktoLHGGvbQvjDeCSCtC\nF3Fo+bgB2B/WTmb9D5WGCjgpxC1uRcwLw8OeZPhmAQKBgQDr87Xq7qoHq2xu64zH\nIpTQjywjRklwMlqsv0g0lsNHU+2BSzLn+KNtlu5Ad92wCXwWCbZ+twoTfcHf6XqL\nuVzkOACDzqamJtHH1YZqW+vC7Y+SKP91LoQS1QyYfZOFebZJEoHaogPYDqCcFsgu\nRHh7tS4YYdFmr3GNL7uWSJluowKBgQDJBWuKRdEel8FcNl5Q0n9sFtqQ1soym55J\naK+cyJHBJV1Mo5/VfI4njV0RrYoRvP5l02mLoi5N0XBEVIdHM+LEcCTRozsDahtj\nbNVcxXWcK0jWFHR6JZf7AWRco+Xz6guoOcC+MbmMP3AQftsz3t2E6Bf4j842GZwT\ngewBnGB62wKBgQDRZIac5xCdndOs2/0i+910+JoC8+1YVFeD1n0Nrn2+Xwz3IPUc\nR9tA9iCZtcZW4xPrutLpwSaABap4O4s1VFrLbaeHUW0zJmAlJ6kR2mFvq8MtwpRy\nOcWbsNZsvYSdf1X1oyb6D625n8GIw+8CoGEL583wdV6P8kKjOSkQRX1kYQKBgQC6\nRgx95+489BDYWwUQzc4HojHMj0x0kuGdUqWQmgb+PJp1HxZIJJAxtHvumqnbgA8Y\n2kvueU2BDLeEifOFFl5m+ygTHrfblSJmAn6/5bXzDeUDg5bfbSClFogilDnMyS8e\nJs4lMDyo6kv07ShAq58Hvm4gBVnnpdmL9hN09qwsiwKBgAecKahDlhqyLyrVky9U\nJkcvoiFCHdfaq9nVsBvGKPrhcgpRHcsdJW5DQWvgVuHxldX5oVcsWTK928t0g7NT\nK2lJUKPzWFjloTtVjwitLSeRzZwiQyuXCdcyQue6nbYFdBj81itYrHyxFUsEdNtO\nVh2hCgwDliUj3b0sgwVqtj7B\n-----END PRIVATE KEY-----\n",
+ "client_email": "firebase-adminsdk-60nyc@shopsync-9ecdc.iam.gserviceaccount.com",
+ "client_id": "111221445997110214622",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-60nyc%40shopsync-9ecdc.iam.gserviceaccount.com",
+ "universe_domain": "googleapis.com"
+}
diff --git a/src/frontend/shopsync-9ecdc-firebase-adminsdk-60nyc-a335ead1ea.json b/src/frontend/shopsync-9ecdc-firebase-adminsdk-60nyc-a335ead1ea.json
new file mode 100644
index 0000000..3c795ac
--- /dev/null
+++ b/src/frontend/shopsync-9ecdc-firebase-adminsdk-60nyc-a335ead1ea.json
@@ -0,0 +1,13 @@
+{
+ "type": "service_account",
+ "project_id": "shopsync-9ecdc",
+ "private_key_id": "a335ead1ea5c35451f12689886264cd6c6e1a059",
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQDcWezpDyRYN8JX\nxMyY0k+N/bTV3WWT6+Z3VT3RYkRMoPWtQ3HHXF9pnYubsLl/q640nEvA1dLZIUZI\nKdAxRdeHjdEV91olvAAsy7WLAMJhJjDrP3vIfMfricsYc/9niFBfivZJP5AgwN3K\nPq6VH77KQrB9DTTGsTk6wPAtH6Doa2tAmxGct6FGgY5nG6Fs8BxXSCzvcfPCLvDv\nngPJaFeloXlA4kDmXPwT66cgC1nUjATN49DC7nw1o9Y8xJ9Jc5v8MxjHTy7C/7Pi\nmK6dyIcUb76zAwa+hc6MINaR4jlAnQWhXvtXm2jKGAZCZ22gyEuvfQLqLX/MgMrL\nW4pkfLBrAgMBAAECggEAAn8tTtRcQqlys4I6embSItpjlsROVWsn/bp/tBUGzMKf\n9IBhEULftsCgp2n9lEmk1fJk2+twVPenStAVhm2YGGQ70YlaSGfLcJ4e9f395p5t\n/2zIIWcgXdeEUPjg18ZoqchLGWjytZXTBGSfk0Z5Z8u7m1BH+HPplUNxTygPR+j8\nxuc8j+RGcri7BzuSyTUybsCI1x0cfq5uqp32Hqk6CfXBg6fxhpYO7yVk7N6wT6ec\nKHF6ngRHXQe1dzDHMGi+iHAT01kkx/nofmKdV+HqHyxPm4rbgQOD0pn1ZEADY1PG\n5cvR8cQjWkV1yVrQ9vF6LDoEkHzHPqcfF4GAsNJYBQKBgQD2TZk1h9nY4isqg40i\n00Vp6PqGPqABCRBm0msGMh+CsVy8byDAnLaIRDlgo+JI/FvzfIwZuoeCyFPTkdgN\nKFNZhykOm43FBLXR9PxGlk7CuUK5ASYmJfxufnDenatU+XfdeaL5xFMRSODexrSq\nFmAS5TEU2e+YLqdyc/r1C2Q7DQKBgQDlBsR73Sg6MGayCrs2ruHW3g8ERtahXODH\nACXf5sACUjsV6jr3aKGR31vnABTcZEjrof0Ni/8LbWtBG6J+bmcjsC5VyMLOC4jh\nmvwIewmtQutzOVLaBkvF3/p7wIt+UteYeSPvbjQAnwi540oEktk9N8qkv2GgzAH/\nRfGVjmtbVwKBgHF45JnN4aZS5FIs0yv1K6iUhj6swWhYta65SEdNdkjuz2ucwvkZ\n+dojnE+SkSDQ6sftXFpKHj45bq0tJt1A881uQJMTRSg8eEunU0Zt3xFE6qFzDxFK\nNNbu968H8rQuTnPBozzwnth6u+bGotstfcuWvZr+oKx66fgHyNl2CxJNAoGAYSda\nyFSL0QthNRu6STsskHKImj7Wo4L7008rwexn/VQWvngrZXKcP34pxTdSoh9kk5iW\n+V0u5xEWk3r+lnWNCSWeskNE6BUajuGpEovnEfm2WZ2ymMxc7mbSIhcO1Zqc3JBe\n/x2Xr7/G+twBNSl6QC7fpr2M06JXIovwLIpK3mcCfxLaw55CjnkF8QdPW22RE53g\n5tRzEHHkrCiQBeDnZAP7mCO8I7bd+7qcsuHi4aVAiRp3aeniUnAehW8W6oLgJiNk\nOLRpPU6IWkjFSyyfyvtYHGOkFJxqmQM6vIMkpQx9BfzjjuZXPk4HGhNq3lfW6lfc\nhZPy6tWfQYc4xwDQrEA=\n-----END PRIVATE KEY-----\n",
+ "client_email": "firebase-adminsdk-60nyc@shopsync-9ecdc.iam.gserviceaccount.com",
+ "client_id": "111221445997110214622",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-60nyc%40shopsync-9ecdc.iam.gserviceaccount.com",
+ "universe_domain": "googleapis.com"
+}
diff --git a/src/frontend/slash_user_interface.py b/src/frontend/slash_user_interface.py
index df0c7ca..fe3c703 100644
--- a/src/frontend/slash_user_interface.py
+++ b/src/frontend/slash_user_interface.py
@@ -7,6 +7,7 @@
# Import Libraries
import os
import sys
+import time
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import streamlit.components.v1 as components
import re
@@ -16,6 +17,8 @@
from main_streamlit import search_items_API
import streamlit as st
from firebase_admin import firestore, auth
+from bs4 import BeautifulSoup
+import requests
# sys.path.append('../')
# st.set_page_config(layout= "wide")
# st.title("ShopSync")
@@ -32,8 +35,144 @@ def create_app(db_client=None):
db_client = get_firestore_client()
def search_product(website, product_name):
- return search_items_API(website, product_name)
+ results = search_items_API(website, product_name)
+ #print(results)
+ return results
+
+
+def search_walmart_product(query):
+ api_key = "YOUR_WALMART_API_KEY"
+ url = f"https://api.walmartlabs.com/v1/search"
+ params = {
+ "query": query,
+ "format": "json",
+ "apiKey": api_key
+ }
+ response = requests.get(url, params=params)
+ if response.status_code == 200:
+ data = response.json()
+ return [{
+ "title": item.get("name"),
+ "price": f"${item.get('salePrice', 'N/A')}",
+ "link": item.get("productUrl"),
+ "image_url": item.get("thumbnailImage", "https://via.placeholder.com/150")
+ } for item in data.get("items", [])]
+ return []
+
+def search_amazon_product(query):
+ url = "https://amazon24.p.rapidapi.com/api/product"
+ headers = {
+ "X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
+ "X-RapidAPI-Host": "amazon24.p.rapidapi.com"
+ }
+ params = {"country": "US", "keyword": query}
+ response = requests.get(url, headers=headers, params=params)
+ if response.status_code == 200:
+ data = response.json()
+ return [{
+ "title": product.get("title"),
+ "price": product.get("price"),
+ "link": product.get("link"),
+ "image_url": product.get("thumbnail")
+ } for product in data.get("docs", [])]
+ return []
+
+def search_ebay_product(query):
+ app_id = "YOUR_EBAY_APP_ID"
+ url = "https://svcs.ebay.com/services/search/FindingService/v1"
+ params = {
+ "OPERATION-NAME": "findItemsByKeywords",
+ "SERVICE-VERSION": "1.0.0",
+ "SECURITY-APPNAME": app_id,
+ "RESPONSE-DATA-FORMAT": "JSON",
+ "keywords": query
+ }
+ response = requests.get(url, params=params)
+ if response.status_code == 200:
+ data = response.json()
+ return [{
+ "title": item.get("title")[0],
+ "price": item.get("sellingStatus", [{}])[0].get("currentPrice", [{}])[0].get("__value__"),
+ "link": item.get("viewItemURL", ["#"])[0],
+ "image_url": item.get("galleryURL", ["https://via.placeholder.com/150"])[0]
+ } for item in data.get("findItemsByKeywordsResponse", [{}])[0].get("searchResult", [{}])[0].get("item", [])]
+ return []
+
+def search_bestbuy_product(query):
+ api_key = "YOUR_BESTBUY_API_KEY"
+ url = f"https://api.bestbuy.com/v1/products((search={query}))"
+ params = {
+ "format": "json",
+ "apiKey": api_key
+ }
+ response = requests.get(url, params=params)
+ if response.status_code == 200:
+ data = response.json()
+ return [{
+ "title": product.get("name"),
+ "price": product.get("salePrice"),
+ "link": product.get("url"),
+ "image_url": product.get("image")
+ } for product in data.get("products", [])]
+ return []
+
+def search_target_product(query):
+ url = "https://target1.p.rapidapi.com/products/v3/search"
+ headers = {
+ "X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
+ "X-RapidAPI-Host": "target1.p.rapidapi.com"
+ }
+ params = {"keyword": query, "count": 10}
+
+ response = requests.get(url, headers=headers, params=params)
+ if response.status_code == 200:
+ data = response.json()
+ return [{
+ "title": item.get("title"),
+ "price": item.get("price", {}).get("formatted_current_price"),
+ "link": item.get("url"),
+ "image_url": item.get("images", [{}])[0].get("base_url") + item.get("images", [{}])[0].get("primary")
+ } for item in data.get("data", {}).get("search", {}).get("products", [])]
+ return []
+
+def search_costco_product(query):
+ url = "https://costco4.p.rapidapi.com/search"
+ headers = {
+ "X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
+ "X-RapidAPI-Host": "costco4.p.rapidapi.com"
+ }
+ params = {"q": query}
+
+ response = requests.get(url, headers=headers, params=params)
+ if response.status_code == 200:
+ data = response.json()
+ return [{
+ "title": item.get("name"),
+ "price": item.get("price"),
+ "link": item.get("url"),
+ "image_url": item.get("image")
+ } for item in data.get("products", [])]
+ return []
+
+
+def fetch_image_from_bing(product_name):
+ api_key = "YOUR_BING_API_KEY"
+ search_url = "https://api.bing.microsoft.com/v7.0/images/search"
+ headers = {"Ocp-Apim-Subscription-Key": api_key}
+ params = {"q": product_name, "count": 1} # Limit to 1 image for speed
+
+ try:
+ response = requests.get(search_url, headers=headers, params=params)
+ if response.status_code == 200:
+ data = response.json()
+ if "value" in data and len(data["value"]) > 0:
+ return data["value"][0]["contentUrl"] # Return the first image URL
+ except Exception as e:
+ print(f"Error fetching image: {e}")
+
+ return "https://via.placeholder.com/150" # Default image if no result
+
def check_product_input(product):
"""Check if the product input is valid based on multiple criteria."""
# Check for non-empty input
@@ -137,15 +276,32 @@ def split_description(description):
margin-top: -40px;
}
/* Adjust the select box width */
+ # .stSelectbox {
+ # font-size: 400px !important;
+ # width: 100% !important;
+ # max-width: 500px;
+ # margin-top: -40px;
+ # }
.stSelectbox {
font-size: 400px !important;
width: 100% !important;
max-width: 500px;
margin-top: -40px;
}
+
+ /* Align checkboxes to the right */
+ div[data-testid="stHorizontalBlock"] {
+ display: flex;
+ justify-content: right; /* Align checkboxes to the right */
+ }
+
+ label {
+ margin-left: auto; /* Push label text to the left */
+ text-align: right; /* Align text to the right */
+ }
.stSlider{
margin-top: -30px;
- margin-left: 5px;
+ margin-left: -65px;
width: 400px !important;}
""",
@@ -245,13 +401,39 @@ def reset_button():
company_list = conf.getCompanies()
# results = search_product(website, product)
results = search_product(website_dict[website], product)
+
+ user = auth.get_user_by_email(st.session_state.user_email) # Replace with actual user email
+ uid = user.uid
+
+ # Reference to the user's document in "hisourites" collection
+ user_his_ref = db.collection("history").document(uid)
+
+ # Get the user's current hisorites data, or create a new structure if it doesn't exist
+ user_his_doc = user_his_ref.get()
+
+ if user_his_doc.exists:
+ # If the document exists, retrieve the current data
+ user_his_data = user_his_doc.to_dict()
+ else:
+ # Initialize empty arrays if document doesn't exist
+ user_his_data = {
+ "Search": [],
+ "Timestamp": []
+ }
+
+ user_his_data["Search"].append(product) # Access the actual value
+ user_his_data["Timestamp"].append(time.time()) # Access the actual value
+
+ # Update the user's document in Firestore with the new data
+ user_his_ref.set(user_his_data)
+
# Use st.columns based on return values
description = []
url = []
price = []
site = []
- # rakuten = []
rating = []
+ image = []
import random
@@ -278,17 +460,20 @@ def get_random_value_from_list(lst):
description.append(result['title'])
url.append(result['link'])
price_str = result['price']
- rating_value = get_random_value_from_list(my_list)
-
+ rating_value = result.get('review', '0') # Safely access 'review'
+ print(rating_value)
+ image.append(result['image_url'])
# Clean and extract price
clean_price_str = re.sub(r'[^\d\.\,]', '', price_str) # Remove unwanted characters
match = re.search(r'(\d{1,3}(?:,\d{3})*(?:\.\d{1,2})?)', clean_price_str)
-
+ rating_matches = re.findall(r"\d+(?:\.\d+)?", rating_value)
+ print(rating_matches)
+ rating_float = [float(match) for match in rating_matches[:1]]
if match:
price_str = match.group(0).replace(',', '') # Remove commas for conversion
price_f = float(price_str)
price.append(price_f)
- rating.append(rating_value) # Append rating only if price is valid
+ rating.append(rating_float) # Append rating only if price is valid
else:
print("Unable to extract a valid price from the string:", price_str)
price.append(None) # Append None if price extraction fails
@@ -300,7 +485,22 @@ def get_random_value_from_list(lst):
if len(price):
dataframe = pd.DataFrame(
- {'Description': description, 'Price': price, 'Link': url, 'Website': site,'Ratings': rating})
+ {'Image_URL': image, 'Description': description, 'Price': price, 'Link': url, 'Website': site,'Ratings': rating})
+
+ def add_http_if_not_present(url):
+ if url.startswith('http://') or url.startswith('https://'):
+ return url
+ else:
+ return 'https://' + url
+ dataframe['Link'] = dataframe['Link'].apply(add_http_if_not_present)
+
+ dataframe['Image'] = dataframe.apply(
+ lambda row: f'',
+ axis=1
+ )
+
+ dataframe = dataframe.drop(["Image_URL", "Link"], axis=1)
+
dataframe['Description'] = dataframe['Description'].apply(
split_description)
dataframe['Product'] = dataframe['Description'].str.split(
@@ -311,18 +511,29 @@ def get_random_value_from_list(lst):
dataframe.insert(0, 'Product', product_column)
dataframe['Price'] = dataframe['Price'].apply(
- lambda x: float(f'{x:.2f}'))
+ lambda x: float(f'{float(x):.2f}') if pd.notnull(x) and str(x).replace('.', '', 1).isdigit() else None
+ )
# dataframe = dataframe.sort_values(by='Price', ascending=True)
dataframe = dataframe.reset_index(drop=True)
- dataframe['Price'] = [f'{x:.2f}' for x in dataframe['Price']]
+ dataframe['Price'] = dataframe['Price'].apply(lambda x: f'{x:.2f}' if x is not None else 'N/A')
+
- def add_http_if_not_present(url):
- if url.startswith('http://') or url.startswith('https://'):
- return url
- else:
- return 'https://' + url
- dataframe['Link'] = dataframe['Link'].apply(add_http_if_not_present)
st.session_state['dataframe'] = dataframe
+
+ styled_table = (
+ dataframe.style
+ .set_properties(**{'text-align': 'center'})
+ .set_table_styles([
+ {"selector": "th", "props": [("text-align", "center")]},
+ {"selector": "td", "props": [("text-align", "center")]}
+ ])
+ )
+
+ # st.markdown(
+ # styled_table.to_html(escape=False), # Allow HTML content
+ # unsafe_allow_html=True
+ # )
+
st.success("Data successfully scraped and cached.")
st.session_state.dataframe = dataframe
@@ -370,7 +581,7 @@ def get_button_indices(button_ix):
st.markdown("