from collections.abc import Iterator from datetime import datetime, timezone from operator import attrgetter from os import execv, fork from subprocess import PIPE, Popen from sys import argv, stderr from inject import autoparams from ytrssil.bindings import setup_dependencies from ytrssil.constants import mpv_options from ytrssil.datatypes import Video from ytrssil.protocols import ChannelRepository, Fetcher def user_query(videos: dict[str, Video], reverse: bool = False) -> list[Video]: p = Popen( ['fzf', '-m'], stdout=PIPE, stdin=PIPE, ) video_list: Iterator[Video] if reverse: video_list = reversed(videos.values()) else: video_list = iter(videos.values()) input_bytes = '\n'.join(map(str, video_list)).encode('UTF-8') stdout, _ = p.communicate(input=input_bytes) videos_str: list[str] = stdout.decode('UTF-8').strip().split('\n') ret: list[Video] = [] for video_str in videos_str: *_, video_id = video_str.split(' - ') try: ret.append(videos[video_id]) except KeyError: pass return ret @autoparams() def fetch_new_videos( repository_manager: ChannelRepository, fetcher: Fetcher, ) -> int: with repository_manager as _: _, new_videos = fetcher.fetch_new_videos() if not new_videos: print('No new videos', file=stderr) return 1 return 0 @autoparams() def watch_videos( repository_manager: ChannelRepository, fetcher: Fetcher, ) -> int: with repository_manager as repository: new_videos = repository.get_new_videos() if not new_videos: print('No new videos', file=stderr) return 1 selected_videos = user_query(new_videos) if not selected_videos: print('No video selected', file=stderr) return 2 video_urls = [video.url for video in selected_videos] cmd = ['/usr/bin/mpv', *mpv_options, *video_urls] if (fork() == 0): execv(cmd[0], cmd) for video in selected_videos: selected_channel = repository.get_channel(video.channel_id) selected_channel.mark_video_as_watched(video) watch_timestamp = datetime.utcnow().replace(tzinfo=timezone.utc) repository.update_video(video, watch_timestamp) return 0 @autoparams() def print_url( repository_manager: ChannelRepository, fetcher: Fetcher, ) -> int: with repository_manager as repository: new_videos = repository.get_new_videos() if not new_videos: print('No new videos', file=stderr) return 1 selected_videos = user_query(new_videos) if not selected_videos: print('No video selected', file=stderr) return 2 for video in selected_videos: selected_channel = repository.get_channel(video.channel_id) selected_channel.mark_video_as_watched(video) watch_timestamp = datetime.utcnow().replace(tzinfo=timezone.utc) repository.update_video(video, watch_timestamp) print(video.url) return 0 @autoparams() def watch_history( repository_manager: ChannelRepository, fetcher: Fetcher, ) -> int: with repository_manager as repository: watched_videos = repository.get_watched_videos() selected_videos = user_query(watched_videos, reverse=True) if not selected_videos: print('No video selected', file=stderr) return 1 video_urls = [video.url for video in selected_videos] cmd = ['/usr/bin/mpv', *mpv_options, *video_urls] if (fork() == 0): execv(cmd[0], cmd) return 0 @autoparams() def mark_as_watched( repository_manager: ChannelRepository, fetcher: Fetcher, up_to_date: datetime ) -> int: with repository_manager as repository: channels, new_videos = fetcher.fetch_new_videos() for video in sorted(new_videos.values(), key=attrgetter('timestamp')): if video.timestamp >= up_to_date: continue selected_channel = channels[video.channel_id] selected_channel.mark_video_as_watched(video) watch_timestamp = datetime.utcnow().replace(tzinfo=timezone.utc) repository.update_video(video, watch_timestamp) return 0 def main(args: list[str] = argv) -> int: setup_dependencies() command: str try: command = args[1] except IndexError: command = 'watch' if command == 'fetch': return fetch_new_videos() elif command == 'watch': return watch_videos() elif command == 'print': return print_url() elif command == 'history': return watch_history() elif command == 'mark': up_to_date = datetime.fromisoformat(args[2]) return mark_as_watched(up_to_date=up_to_date) else: print(f'Unknown command "{command}"', file=stderr) return 1 return 0