Skip to content

Commit 7c74529

Browse files
committed
Bot stuff
1 parent c5fc8ad commit 7c74529

File tree

2 files changed

+207
-0
lines changed

2 files changed

+207
-0
lines changed

app.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,119 @@ def render_calendar_view(data: List[Dict]) -> None:
694694
st.markdown("💡 **2-hour slots** are shown as longer blocks covering 2 consecutive hours")
695695

696696

697+
def render_alerts_section() -> None:
698+
"""Render the alerts/subscriptions section with form and subscription list."""
699+
supabase = get_supabase_client()
700+
if not supabase:
701+
st.warning("⚠️ Supabase not available. Alerts require Supabase connection.")
702+
return
703+
704+
# Initialize chat_id in session state if not present
705+
if 'alert_chat_id' not in st.session_state:
706+
st.session_state['alert_chat_id'] = ""
707+
708+
# Form to add new subscription
709+
with st.form("add_subscription_form"):
710+
st.markdown("### Add New Alert")
711+
chat_id = st.text_input("Chat ID", value=st.session_state['alert_chat_id'], help="Your Telegram chat ID")
712+
day_of_week = st.selectbox(
713+
"Day of Week",
714+
options=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
715+
)
716+
col1, col2 = st.columns(2)
717+
with col1:
718+
start_hour = st.number_input("Start Hour", min_value=0, max_value=23, value=8, step=1, help="Hour in 24-hour format (0-23)")
719+
with col2:
720+
end_hour = st.number_input("End Hour", min_value=0, max_value=23, value=22, step=1, help="Hour in 24-hour format (0-23)")
721+
722+
submitted = st.form_submit_button("Add Alert")
723+
724+
if submitted:
725+
if not chat_id:
726+
st.error("Please enter a Chat ID")
727+
elif start_hour > end_hour:
728+
st.error("Start hour must be less than or equal to end hour")
729+
else:
730+
try:
731+
# Store chat_id in session state
732+
st.session_state['alert_chat_id'] = chat_id
733+
734+
# Insert subscription into Supabase
735+
response = supabase.table('subscriptions').insert({
736+
'chat_id': chat_id,
737+
'day_of_week': day_of_week,
738+
'start_hour': start_hour,
739+
'end_hour': end_hour
740+
}).execute()
741+
742+
st.success(f"✅ Alert added successfully!")
743+
st.rerun()
744+
except Exception as e:
745+
st.error(f"Failed to add alert: {e}")
746+
747+
# Display current subscriptions
748+
st.markdown("### My Alerts")
749+
750+
# Search box for Chat ID
751+
col_search1, col_search2 = st.columns([3, 1])
752+
with col_search1:
753+
search_chat_id = st.text_input("Search by Chat ID", key="search_chat_id", placeholder="Enter your Telegram Chat ID", help="Enter your Chat ID to view and manage your alerts")
754+
with col_search2:
755+
search_button = st.button("Search", key="search_alerts_button", use_container_width=True)
756+
757+
# Initialize search result in session state
758+
if 'searched_chat_id' not in st.session_state:
759+
st.session_state['searched_chat_id'] = None
760+
761+
# Update searched_chat_id when search button is clicked
762+
if search_button:
763+
if search_chat_id:
764+
st.session_state['searched_chat_id'] = search_chat_id
765+
else:
766+
st.warning("Please enter a Chat ID to search")
767+
st.session_state['searched_chat_id'] = None
768+
769+
# Get the chat_id to filter by (from search)
770+
chat_id_filter = st.session_state.get('searched_chat_id', None)
771+
772+
if chat_id_filter:
773+
try:
774+
# Fetch subscriptions for this chat_id
775+
response = supabase.table('subscriptions').select("*").eq('chat_id', chat_id_filter).execute()
776+
777+
if response.data and len(response.data) > 0:
778+
st.success(f"Found {len(response.data)} alert(s) for Chat ID: {chat_id_filter}")
779+
for sub in response.data:
780+
col1, col2, col3 = st.columns([3, 1, 1])
781+
with col1:
782+
st.write(f"**{sub['day_of_week']}** {sub['start_hour']:02d}:00 - {sub['end_hour']:02d}:00")
783+
with col2:
784+
# Delete button
785+
sub_id = sub.get('id') or f"{sub['chat_id']}_{sub['day_of_week']}_{sub['start_hour']}_{sub['end_hour']}"
786+
if st.button("Delete", key=f"delete_{sub_id}"):
787+
try:
788+
# Try to delete by id first, then by all fields
789+
if 'id' in sub:
790+
delete_response = supabase.table('subscriptions').delete().eq('id', sub['id']).execute()
791+
else:
792+
# Delete by matching all fields
793+
delete_response = supabase.table('subscriptions').delete().eq('chat_id', sub['chat_id']).eq('day_of_week', sub['day_of_week']).eq('start_hour', sub['start_hour']).eq('end_hour', sub['end_hour']).execute()
794+
st.success("✅ Alert deleted!")
795+
st.rerun()
796+
except Exception as e:
797+
st.error(f"Failed to delete alert: {e}")
798+
with col3:
799+
st.write("") # Spacer
800+
else:
801+
st.info(f"No alerts found for Chat ID: {chat_id_filter}")
802+
except Exception as e:
803+
st.error(f"Failed to load alerts: {e}")
804+
elif search_button and not search_chat_id:
805+
st.info("Please enter a Chat ID and click Search to view alerts.")
806+
elif not chat_id_filter:
807+
st.info("Enter a Chat ID above and click Search to view and manage your alerts.")
808+
809+
697810
def render_feed(data: List[Dict], notifications_enabled: bool, topic: str, ntfy_url: str) -> None:
698811
grouped = group_by_day(data)
699812
for day, entries in grouped.items():
@@ -835,6 +948,11 @@ def main() -> None:
835948
st.sidebar.subheader("Notification Log")
836949
for item in st.session_state["ntfy_log"][-10:][::-1]:
837950
st.sidebar.write(item)
951+
952+
# Alerts section
953+
st.markdown("---")
954+
st.subheader("🔔 Alerts")
955+
render_alerts_section()
838956

839957

840958
if __name__ == "__main__":

scraper.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import pandas as pd
1212
from playwright.async_api import async_playwright, Page, TimeoutError as PlaywrightTimeoutError
1313
import pytz
14+
import requests
1415
from supabase import create_client, Client
1516
from dotenv import load_dotenv
1617

@@ -1409,6 +1410,91 @@ def load_cache(path: Path = DATA_PATH) -> tuple[List[Dict], str]:
14091410
return [], 'none'
14101411

14111412

1413+
def process_notifications(new_slots: List[Dict]) -> None:
1414+
"""Process notifications for matching subscriptions.
1415+
1416+
Args:
1417+
new_slots: List of dictionaries with scraped court slot data
1418+
"""
1419+
supabase = get_supabase_client()
1420+
if not supabase:
1421+
logging.warning("Supabase client not available, skipping notifications")
1422+
return
1423+
1424+
bot_token = os.getenv("BOT_TOKEN")
1425+
if not bot_token:
1426+
logging.warning("BOT_TOKEN not set, skipping notifications")
1427+
return
1428+
1429+
try:
1430+
# Fetch all subscriptions
1431+
response = supabase.table('subscriptions').select("*").execute()
1432+
subscriptions = response.data if response.data else []
1433+
1434+
if not subscriptions:
1435+
logging.debug("No subscriptions found, skipping notifications")
1436+
return
1437+
1438+
logging.info(f"Processing notifications for {len(subscriptions)} subscriptions and {len(new_slots)} slots")
1439+
1440+
# Process each slot
1441+
for slot in new_slots:
1442+
time_str = slot.get('time', '')
1443+
if not time_str:
1444+
continue
1445+
1446+
# Parse time string to datetime
1447+
dt_object = None
1448+
time_formats = [
1449+
"%Y-%m-%d %I:%M %p", # "2025-12-18 08:00 AM"
1450+
"%Y-%m-%d %H:%M", # "2025-12-18 08:00"
1451+
]
1452+
1453+
for fmt in time_formats:
1454+
try:
1455+
dt_object = datetime.strptime(time_str, fmt)
1456+
break
1457+
except ValueError:
1458+
continue
1459+
1460+
if dt_object is None:
1461+
continue
1462+
1463+
# Extract day name and hour (local time, no timezone conversion)
1464+
day_name = dt_object.strftime("%A") # Monday, Tuesday, etc.
1465+
hour = dt_object.hour # 0-23
1466+
1467+
# Check against each subscription
1468+
for sub in subscriptions:
1469+
sub_day = sub.get('day_of_week', '')
1470+
start_hour = sub.get('start_hour', 0)
1471+
end_hour = sub.get('end_hour', 23)
1472+
chat_id = sub.get('chat_id', '')
1473+
1474+
# Check if day matches and hour is within range
1475+
if day_name == sub_day and start_hour <= hour <= end_hour:
1476+
# Match found! Send Telegram notification
1477+
try:
1478+
message = f"🎾 *Match Found!*\n\n{slot.get('court', 'Court')} is available {day_name} at {time_str}.\n{slot.get('link', '#')}"
1479+
1480+
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
1481+
payload = {
1482+
'chat_id': chat_id,
1483+
'text': message,
1484+
'parse_mode': 'Markdown'
1485+
}
1486+
1487+
response = requests.post(url, json=payload, timeout=10)
1488+
response.raise_for_status()
1489+
1490+
logging.info(f"Sent notification to chat_id {chat_id} for {slot.get('court')} at {time_str}")
1491+
except Exception as e:
1492+
logging.error(f"Failed to send notification to chat_id {chat_id}: {e}")
1493+
1494+
except Exception as e:
1495+
logging.error(f"Error processing notifications: {e}")
1496+
1497+
14121498
def save_cache(data: List[Dict], path: Path = DATA_PATH) -> None:
14131499
"""Save cache to both JSON file (backup) and Supabase."""
14141500
# Always save to JSON file as backup
@@ -1417,6 +1503,9 @@ def save_cache(data: List[Dict], path: Path = DATA_PATH) -> None:
14171503

14181504
# Also save to Supabase
14191505
clean_and_upload_to_supabase(data)
1506+
1507+
# Process notifications after saving
1508+
process_notifications(data)
14201509

14211510

14221511
async def main(court_names: Optional[List[str]] = None) -> None:

0 commit comments

Comments
 (0)