diff --git a/main.py b/main.py index f62d30a..1260322 100755 --- a/main.py +++ b/main.py @@ -303,7 +303,38 @@ class IcecastBot: Returns: bool: True if the song should be announced, False if it matches any ignore patterns. """ - return not any(pattern.lower() in song.lower() for pattern in self.ignore_patterns) + try: + if not song: + print("Empty song title, not announcing") + return False + + if not self.ignore_patterns: + print("No ignore patterns configured, announcing all songs") + return True + + # Check each pattern + for pattern in self.ignore_patterns: + try: + if not pattern: + continue + if not isinstance(pattern, str): + print(f"Invalid ignore pattern (not a string): {pattern}") + continue + if pattern.lower() in song.lower(): + print(f"Song '{song}' matched ignore pattern '{pattern}', not announcing") + return False + except Exception as e: + print(f"Error checking ignore pattern '{pattern}': {str(e)}") + continue + + print(f"Song '{song}' passed all ignore patterns, will announce") + return True + except Exception as e: + print(f"Exception in should_announce_song: {str(e)}") + import traceback + traceback.print_exc() + # Default to not announcing if there's an error + return False def setup_handlers(self): """Set up all IRC event handlers and command patterns. @@ -731,6 +762,7 @@ class IcecastBot: str: The current song title, or an error message if fetching failed. """ try: + print(f"Fetching metadata from {self.stream_url}/{self.stream_endpoint}") # Try different URL patterns base_urls = [ self.stream_url, # Original URL @@ -741,33 +773,53 @@ class IcecastBot: for base_url in base_urls: try: url = f"{base_url}/status-json.xsl" + print(f"Trying URL: {url}") async with aiohttp.ClientSession() as session: - async with session.get(url) as response: + async with session.get(url, timeout=10) as response: if response.status == 200: data = await response.text() + print(f"Received response from {url}") + + try: + json_data = json.loads(data) + if 'icestats' in json_data: + sources = json_data['icestats'].get('source', []) + if not isinstance(sources, list): + sources = [sources] + + # Find our stream + for src in sources: + if src.get('listenurl', '').endswith(self.stream_endpoint): + title = src.get('title') or src.get('song') or src.get('current_song') + if title: + print(f"Found title: {title}") + return title + + except json.JSONDecodeError as e: + print(f"JSON decode error for {url}: {str(e)}") + continue - json_data = json.loads(data) - if 'icestats' in json_data: - sources = json_data['icestats'].get('source', []) - if not isinstance(sources, list): - sources = [sources] - - # Find our stream - for src in sources: - if src.get('listenurl', '').endswith(self.stream_endpoint): - title = src.get('title') or src.get('song') or src.get('current_song') - if title: - return title - except aiohttp.ClientError as e: + print(f"Client error for {url}: {str(e)}") continue except json.JSONDecodeError as e: + print(f"JSON decode error for {url}: {str(e)}") + continue + except asyncio.TimeoutError: + print(f"Timeout fetching metadata from {url}") + continue + except Exception as e: + print(f"Unexpected error fetching from {url}: {str(e)}") continue + print("All URL patterns failed, returning 'Unable to fetch metadata'") return "Unable to fetch metadata" except Exception as e: - return "Error fetching metadata" + print(f"Exception in fetch_json_metadata: {str(e)}") + import traceback + traceback.print_exc() + return f"Error fetching metadata: {str(e)}" async def monitor_metadata(self): """Monitor the Icecast stream for metadata changes. @@ -826,14 +878,45 @@ class IcecastBot: # Look for metadata marker but fetch from JSON if b"StreamTitle='" in buffer: - new_song = await self.fetch_json_metadata() - if new_song and new_song != self.current_song and "Unable to fetch metadata" not in new_song: - self.current_song = new_song - await self.announce_song(new_song) + try: + print("Detected StreamTitle marker in buffer, fetching metadata") + new_song = await self.fetch_json_metadata() + + # Check if we should announce the song + if new_song and new_song != self.current_song and "Unable to fetch metadata" not in new_song: + print(f"Song changed from '{self.current_song}' to '{new_song}'") + self.current_song = new_song + + # Try to announce the song + try: + await self.announce_song(new_song) + except Exception as e: + print(f"Error announcing song: {str(e)}") + import traceback + traceback.print_exc() + else: + # No song change or unable to fetch metadata + print(f"No song change detected or unable to fetch metadata: {new_song}") - # Clear buffer after metadata marker - buffer = buffer[buffer.find(b"';", buffer.find(b"StreamTitle='")) + 2:] - last_json_check = current_time + # Clear buffer after metadata marker + marker_pos = buffer.find(b"StreamTitle='") + end_pos = buffer.find(b"';", marker_pos) + if end_pos > marker_pos: + buffer = buffer[end_pos + 2:] + print("Buffer cleared after metadata marker") + else: + print(f"Could not find end of metadata marker, truncating buffer") + buffer = buffer[-8192:] # Keep last 8KB to avoid losing the end marker + + # Update last check time + last_json_check = current_time + + except Exception as e: + print(f"Error processing metadata marker: {str(e)}") + import traceback + traceback.print_exc() + # Reset buffer to avoid getting stuck in a loop + buffer = b"" # Keep buffer size reasonable if len(buffer) > 65536: @@ -841,14 +924,34 @@ class IcecastBot: # Fallback JSON check if ICY updates aren't coming through if current_time - last_json_check >= json_check_interval: - new_song = await self.fetch_json_metadata() - if "Unable to fetch metadata" in new_song: - if time.time() - last_data_received > data_timeout: - break - if new_song and new_song != self.current_song: - self.current_song = new_song - await self.announce_song(new_song) - last_json_check = current_time + try: + print("Performing fallback JSON check") + new_song = await self.fetch_json_metadata() + + if "Unable to fetch metadata" in new_song: + print("Unable to fetch metadata in fallback check") + if time.time() - last_data_received > data_timeout: + print("Data timeout exceeded, breaking loop") + break + elif new_song and new_song != self.current_song: + print(f"Song changed in fallback check from '{self.current_song}' to '{new_song}'") + self.current_song = new_song + try: + await self.announce_song(new_song) + except Exception as e: + print(f"Error announcing song in fallback check: {str(e)}") + import traceback + traceback.print_exc() + else: + print(f"No song change detected in fallback check: {new_song}") + + last_json_check = current_time + except Exception as e: + print(f"Error in fallback JSON check: {str(e)}") + import traceback + traceback.print_exc() + # Still update the check time to avoid rapid retries + last_json_check = current_time await asyncio.sleep(0.1) @@ -888,12 +991,31 @@ class IcecastBot: - The announcement format is configured """ try: + print(f"Attempting to announce song: {song}") if self.channel and self.should_announce_song(song): + print(f"Song passed filters, preparing to announce") # Use the stored channel object directly if hasattr(self.channel, 'name') and self.channel.name.startswith('#'): - await self.channel.message(self.reply.format(song=song)) + try: + formatted_message = self.reply.format(song=song) + print(f"Sending message to channel {self.channel.name}: {formatted_message}") + await self.channel.message(self.reply.format(song=song)) + print(f"Successfully announced song: {song}") + except Exception as e: + print(f"Error sending message to channel: {str(e)}") + import traceback + traceback.print_exc() + else: + print(f"Channel object invalid or not a channel: {self.channel}") + else: + if not self.channel: + print("Channel object is None or invalid") + elif not self.should_announce_song(song): + print(f"Song '{song}' matched ignore patterns, not announcing") except Exception as e: - pass + print(f"Exception in announce_song: {str(e)}") + import traceback + traceback.print_exc() async def start(self): """Start the IRC bot and begin processing events."""