import gradio as gr import requests import json import datetime import re from bs4 import BeautifulSoup from typing import Optional, Dict, Any from urllib.parse import quote, urljoin class ShabbatCandleLightingAgent: """ AI Agent for finding Shabbat candle lighting times worldwide using Chabad.org """ def __init__(self): self.base_url = "https://www.chabad.org" self.candle_lighting_url = "https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm" def search_city_on_chabad(self, city_name: str) -> Optional[str]: """ Search for a city on Chabad.org and get the specific URL for candle lighting times """ try: # Clean and format city name city_clean = city_name.strip() search_query = quote(city_clean) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1' } # Try different search approaches for Chabad.org search_strategies = [ # Direct city page approach f"https://www.chabad.org/calendar/candlelighting_cdo/jewish/{search_query}.htm", # Search with city parameter f"https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm?city={search_query}", # Alternative format f"https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Candle-Lighting-Times-{search_query}.htm", # General search f"https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm" ] for url in search_strategies: try: response = requests.get(url, headers=headers, timeout=15) if response.status_code == 200: # Check if the response contains candle lighting information content_lower = response.text.lower() if any(keyword in content_lower for keyword in ['candle lighting', 'light candles', 'shabbat', 'friday']): return url except Exception as e: print(f"Failed to fetch {url}: {e}") continue # Try POST request to the main search page try: post_data = { 'city': city_clean, 'submit': 'Get Times' } response = requests.post(self.candle_lighting_url, data=post_data, headers=headers, timeout=15) if response.status_code == 200 and 'candle lighting' in response.text.lower(): return response.url except Exception as e: print(f"POST request failed: {e}") except Exception as e: print(f"City search error: {e}") return self.candle_lighting_url # Return main page as fallback def extract_candle_lighting_times(self, html_content: str, city_name: str) -> Dict[str, Any]: """ Extract candle lighting times from Chabad.org HTML content using multiple strategies """ try: soup = BeautifulSoup(html_content, 'html.parser') text = soup.get_text().lower() # Multiple patterns to find candle lighting times candle_patterns = [ r'candle\s+lighting[:\s]*(\d{1,2}:\d{2}(?:\s*[ap]m)?)', r'light\s+candles[:\s]*(\d{1,2}:\d{2}(?:\s*[ap]m)?)', r'friday[:\s]*(\d{1,2}:\d{2}(?:\s*[ap]m)?)', r'(\d{1,2}:\d{2}\s*[ap]m?)(?=.*candle)', r'(\d{1,2}:\d{2}\s*[ap]m?)(?=.*light)', r'(\d{1,2}:\d{2}\s*[ap]m?)(?=.*friday)' ] # Patterns for Havdalah times havdalah_patterns = [ r'havdalah[:\s]*(\d{1,2}:\d{2}(?:\s*[ap]m)?)', r'saturday[:\s]*(\d{1,2}:\d{2}(?:\s*[ap]m)?)', r'(\d{1,2}:\d{2}\s*[ap]m?)(?=.*havdalah)', r'(\d{1,2}:\d{2}\s*[ap]m?)(?=.*saturday)' ] # Find candle lighting time candle_time = None for pattern in candle_patterns: match = re.search(pattern, text, re.IGNORECASE) if match: candle_time = match.group(1) break # Find Havdalah time havdalah_time = None for pattern in havdalah_patterns: match = re.search(pattern, text, re.IGNORECASE) if match: havdalah_time = match.group(1) break # Look for times in common HTML elements if not candle_time or not havdalah_time: # Check for time elements with specific classes or IDs time_selectors = [ '[class*="time"]', '[class*="candle"]', '[class*="light"]', '[id*="time"]', '[id*="candle"]', 'td', 'span', 'div' ] for selector in time_selectors: elements = soup.select(selector) for element in elements: element_text = element.get_text().strip() time_match = re.search(r'\d{1,2}:\d{2}(?:\s*[ap]m)?', element_text, re.IGNORECASE) if time_match: time_found = time_match.group() if not candle_time and any(word in element_text.lower() for word in ['candle', 'light', 'friday']): candle_time = time_found elif not havdalah_time and any(word in element_text.lower() for word in ['havdalah', 'saturday']): havdalah_time = time_found # Calculate current Shabbat dates now = datetime.datetime.now() days_until_friday = (4 - now.weekday()) % 7 if days_until_friday == 0 and now.hour >= 18: # If it's Friday evening, get next Friday days_until_friday = 7 friday = now + datetime.timedelta(days=days_until_friday) saturday = friday + datetime.timedelta(days=1) return { 'city': city_name, 'candle_lighting_time': candle_time, 'havdalah_time': havdalah_time, 'friday_date': friday.strftime('%B %d, %Y'), 'saturday_date': saturday.strftime('%B %d, %Y'), 'found_data': bool(candle_time or havdalah_time), 'search_successful': True } except Exception as e: print(f"Time extraction error: {e}") return { 'city': city_name, 'candle_lighting_time': None, 'havdalah_time': None, 'friday_date': None, 'saturday_date': None, 'found_data': False, 'search_successful': False, 'error': str(e) } def get_backup_times(self, city_name: str) -> Optional[Dict[str, Any]]: """ Try alternative sources for Shabbat times when Chabad.org fails """ try: # Alternative Jewish calendar websites backup_urls = [ f"https://www.hebcal.com/shabbat?city={quote(city_name)}", f"https://www.myzmanim.com/search.aspx?city={quote(city_name)}" ] headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } for url in backup_urls: try: response = requests.get(url, headers=headers, timeout=10) if response.status_code == 200: times = self.extract_candle_lighting_times(response.text, city_name) if times.get('found_data'): times['source'] = 'backup' return times except Exception as e: print(f"Backup source {url} failed: {e}") continue except Exception as e: print(f"Backup sources error: {e}") return None def get_shabbat_times(self, city_input: str = "") -> str: """ Main function to get Shabbat candle lighting times """ try: city_name = city_input.strip() if not city_name: return self._format_error_response("❌ Please enter a city name to search for Shabbat times.") # Search Chabad.org for the city print(f"🔍 Searching for Shabbat times in: {city_name}") city_url = self.search_city_on_chabad(city_name) result_data = None source = "Chabad.org" if city_url: try: headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } response = requests.get(city_url, headers=headers, timeout=15) if response.status_code == 200: result_data = self.extract_candle_lighting_times(response.text, city_name) print(f"📊 Extraction result: {result_data}") except Exception as e: print(f"❌ Chabad.org fetch error: {e}") # Try backup sources if Chabad.org didn't work well if not result_data or not result_data.get('found_data'): print("🔄 Trying backup sources...") backup_data = self.get_backup_times(city_name) if backup_data and backup_data.get('found_data'): result_data = backup_data source = "Alternative Jewish calendar source" # Format and return the response if result_data and result_data.get('found_data'): return self._format_success_response(result_data, source, city_url) else: return self._format_not_found_response(city_name, city_url) except Exception as e: print(f"Main function error: {e}") return self._format_error_response(f"❌ An error occurred: {str(e)}") def _format_success_response(self, data: Dict[str, Any], source: str, url: str = None) -> str: """Format successful response with Shabbat times""" candle_time = data.get('candle_lighting_time', 'Not found') havdalah_time = data.get('havdalah_time', 'Not found') response = f""" # 🕯️ Shabbat Candle Lighting Times ## 📍 Location: {data.get('city', 'Unknown')} ### 🕯️ **Candle Lighting (Friday)** **Time:** {candle_time} **Date:** {data.get('friday_date', 'This Friday')} ### ⭐ **Havdalah (Saturday)** **Time:** {havdalah_time} **Date:** {data.get('saturday_date', 'This Saturday')} --- ## 📋 **Important Notes:** - 🕯️ Light Shabbat candles **18 minutes before sunset** on Friday - ⭐ Havdalah is performed after **3 stars appear** on Saturday night - 🌍 Times are specific to your geographic location - ⏰ Always verify times with your local rabbi or Jewish community ## 📚 **Source Information:** **Data from:** {source} **Retrieved:** {datetime.datetime.now().strftime('%B %d, %Y at %I:%M %p')} {f"**URL:** {url}" if url else ""} --- ## 🙏 **Shabbat Shalom!** *May your Shabbat be filled with peace, joy, and spiritual connection.* **🔔 Reminder:** Set an alarm 30 minutes before candle lighting time to prepare properly. """ return response def _format_not_found_response(self, city_name: str, url: str = None) -> str: """Format response when times are not found""" return f""" # 🕯️ Shabbat Candle Lighting Times ## ❌ Times Not Found for {city_name} We searched multiple sources but couldn't find specific Shabbat candle lighting times for **{city_name}**. ### 🔍 **What you can try:** 1. **Check spelling** - Make sure the city name is spelled correctly 2. **Add country** - Try "City, Country" format (e.g., "Paris, France") 3. **Use major city** - Try the nearest major city instead 4. **Alternative spellings** - Try different variations of the city name ### 📍 **General Guidelines for {city_name}:** - 🕯️ **Candle lighting:** Typically 18 minutes before sunset on Friday - ⭐ **Havdalah:** Approximately 25-42 minutes after sunset on Saturday - 🌍 **Location matters:** Times vary significantly by geographic location ### 🌐 **Manual Resources:** - 📱 **Chabad.org:** [Direct search]({url or 'https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm'}) - 📱 **Hebcal.com:** Alternative Jewish calendar - 📞 **Local contacts:** Your synagogue or Chabad house ### 📞 **Local Resources:** - Contact your local Chabad house or synagogue - Check with your Jewish community center - Consult your rabbi for precise local customs --- **Retrieved:** {datetime.datetime.now().strftime('%B %d, %Y at %I:%M %p')} """ def _format_error_response(self, error_message: str) -> str: """Format error response""" return f""" # 🕯️ Shabbat Candle Lighting Times ## ⚠️ Error {error_message} ### 🔄 **Please try:** 1. **Refresh** and try again 2. **Check internet connection** 3. **Use a different city name** 4. **Visit Chabad.org directly:** [Candle Lighting Times](https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm) ### 📞 **Alternative:** Contact your local synagogue or Chabad house for accurate times. --- **Time:** {datetime.datetime.now().strftime('%B %d, %Y at %I:%M %p')} """ def create_shabbat_interface(): """Create the Gradio interface for the Shabbat Candle Lighting Agent""" agent = ShabbatCandleLightingAgent() with gr.Blocks(title="🕯️ Shabbat Candle Lighting Times", theme=gr.themes.Soft()) as app: gr.Markdown(""" # 🕯️ Shabbat Candle Lighting Times AI Agent **Get accurate Shabbat candle lighting and Havdalah times for any city worldwide** This AI agent searches Chabad.org and other reliable Jewish sources to find precise ritual times for your location. --- """) with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📍 Location Input") city_input = gr.Textbox( label="🏙️ City Name", placeholder="Enter city name (e.g., 'New York', 'London, UK', 'Tel Aviv, Israel')", info="Please enter your city name to get accurate Shabbat times", lines=1 ) search_btn = gr.Button( "🔍 Get Shabbat Times", variant="primary", size="lg" ) gr.Markdown(""" ### 💡 **City Input Tips:** - Use full city names for better results - Add country for common city names (e.g., "Paris, France") - Try nearest major city if yours isn't found - Use English city names for best search results """) with gr.Column(scale=2): output_area = gr.Markdown( label="📋 Shabbat Times Results", value=""" # 🕯️ Welcome! 👆 **Enter your city name above and click 'Get Shabbat Times'** --- **🕯️ Every Friday evening, Jewish families around the world light candles to welcome Shabbat** """ ) # Button click handlers search_btn.click( fn=agent.get_shabbat_times, inputs=[city_input], outputs=output_area ) # Resources and information with gr.Accordion("📚 Resources & Information", open=False): gr.Markdown(""" ### 🕯️ **About Shabbat Candle Lighting:** **Candle Lighting Time:** - Performed on Friday evening before sunset - Typically 18 minutes before sunset (varies by community) - Marks the beginning of Shabbat - Usually done by the woman of the house **Havdalah Time:** - Performed on Saturday evening after sunset - When three medium-sized stars are visible in the sky - Marks the end of Shabbat - Includes blessings over wine, spices, and candle ### 🌐 **Data Sources:** - **Primary:** [Chabad.org](https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm) - **Backup:** Hebcal.com and other Jewish calendar sites - **Method:** Intelligent web scraping with multiple fallback strategies ### ⚠️ **Important Disclaimers:** - Always consult your local rabbi for specific customs - Times may vary based on local Jewish community practices - This tool is for reference only - not a substitute for religious guidance - Some communities light candles at different intervals before sunset ### 🔧 **Technical Features:** - Multiple search strategies for maximum success on Chabad.org - Intelligent pattern matching to extract candle lighting times - Fallback to alternative Jewish calendar sources if needed - Manual city input for maximum accuracy and reliability """) gr.Markdown(""" --- **🙏 Shabbat Shalom!** | Built with ❤️ for the global Jewish community *For questions about Jewish law or local customs, please consult your rabbi or local Jewish community.* """) return app # Main execution if __name__ == "__main__": app = create_shabbat_interface() # Launch the application app.launch( server_name="0.0.0.0", server_port=7860, share=False, debug=False, show_error=True )