diff --git a/main.py b/main.py index 072f91d..37d5473 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,93 @@ logging.basicConfig( format='[%(asctime)s] %(levelname)s %(module)s/%(funcName)s - %(message)s', level=logging.DEBUG if DEBUG else logging.INFO) -import requests +import asyncio +import re +from datetime import datetime, timedelta, timezone -def main(): - pass +import aiodocker + +NAVIDROME_CONTAINER = os.environ.get('NAVIDROME_CONTAINER', 'navidrome-1') +STAR_UNSTAR_WINDOW = timedelta(seconds=4) + +# A dictionary to store the timestamp of when a song was starred. +# {song_id: timestamp} +starred_songs = {} + +LOG_PATTERN = re.compile( + r'time="(?P[^"]+)".*msg="Changing starred" ids="\[`(?P[^`]+)`\]".*starred=(?Ptrue|false)' +) + + +def parse_log_line(line): + """ + Parses a log line to find song star/unstar events. + Returns a tuple of (timestamp, song_id, is_starred) or None if not a match. + """ + match = LOG_PATTERN.search(line) + if not match: + return None + + data = match.groupdict() + ts_str = data['ts'] + song_id = data['id'] + is_starred = data['starred'] == 'true' + + # fromisoformat doesn't like Z, so we replace it with +00:00 timezone info. + timestamp = datetime.fromisoformat(ts_str.replace('Z', '+00:00')) + + return timestamp, song_id, is_starred + + +async def main(): + """ + Monitors Navidrome container logs for rapid star/unstar events. + """ + docker = None + try: + docker = aiodocker.Docker() + container = await docker.containers.get(NAVIDROME_CONTAINER) + + logging.info(f"Monitoring logs for container '{NAVIDROME_CONTAINER}'...") + logs = container.log( + stdout=True, + stderr=True, + follow=True, + since=datetime.now(timezone.utc).timestamp() + ) + + async for line in logs: + parsed = parse_log_line(line) + if not parsed: + continue + + timestamp, song_id, is_starred = parsed + + if is_starred: + starred_songs[song_id] = timestamp + elif song_id in starred_songs: # is_starred is False (unstarred) + time_diff = timestamp - starred_songs[song_id] + if time_diff <= STAR_UNSTAR_WINDOW: + logging.info( + f"Song {song_id} was starred and then unstarred within {STAR_UNSTAR_WINDOW.seconds} seconds." + ) + # Remove song from tracking after it has been unstarred + del starred_songs[song_id] + + except aiodocker.exceptions.DockerError as e: + if e.status == 404: + logging.error(f"Container '{NAVIDROME_CONTAINER}' not found.") + else: + logging.error(f"Error connecting to Docker or getting container: {e}") + except Exception as e: + logging.error(f"An unexpected error occurred: {e}") + finally: + if docker: + await docker.close() + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + logging.info("Exiting.")