diff --git a/config.json.example b/config.json.example index 56e712f..67d13e7 100644 --- a/config.json.example +++ b/config.json.example @@ -147,6 +147,9 @@ "positive": ["I like it."], "negative": ["I don't like it."] }, + "playlistoftheday": { + "commands": ["playlist of the day"] + }, "plus": { "commands": ["plus"], "aliases": { diff --git a/edmond/plugins/playlist_of_the_day.py b/edmond/plugins/playlist_of_the_day.py new file mode 100644 index 0000000..202f972 --- /dev/null +++ b/edmond/plugins/playlist_of_the_day.py @@ -0,0 +1,66 @@ +import datetime + +from edmond.plugin import Plugin + + +class PlaylistOfTheDayPlugin(Plugin): + """Collect music links from other platforms. + + Plugins that want to feed the playlist must do so by calling the add_link method of + the plugin. Later this plugin may feed its playlist using links unhandled by other, + more specialized plugins (bandcamp, soundcloud, …). + + The current behaviour for the playlist is that links are added for a same day, so + the date at which the latest link was added is also stored. If a link is the first + to be added on a new day (by using this date as reference), the previous list is + emptied. But if the list is requested even if no links have been posted today and + the playlist is therefore outdated, it is still posted by the bot, along with a + message explaining that it's not the current day's playlist. + + Note that the playlist itself is just of bunch of lines, which should of course + contain links, but can also contain titles and other metadata. + """ + + REQUIRED_CONFIGS = ["commands"] + DATE_KEY = "date_of_latest_link" + PLAYLIST_KEY = "current_playlist" + + def __init__(self, bot): + super().__init__(bot) + + def on_pubmsg(self, event): + if not self.should_handle_command(event.arguments[0], no_content=True): + return False + + # self.post_playlist(event.target) + return True + + def add_line(self, line: str) -> None: + """Add this line to the current playlist.""" + today = datetime.date.today() + last_update_str = self.get_storage_value(self.DATE_KEY) + if last_update_str: + last_update_date = datetime.date.fromisoformat(last_update_str) + else: + last_update_date = today + + if last_update_date == today: + current_playlist: list[str] = self.get_storage_value( + self.PLAYLIST_KEY, + default=[], + ) + current_playlist.append(line) + else: + current_playlist = [line] + self.set_storage_value( + self.PLAYLIST_KEY, + current_playlist, + skip_save=True, # save one write + ) + self.set_storage_value( + self.DATE_KEY, + today.isoformat(), + ) + + # def post_playlist(self): + # pass diff --git a/edmond/plugins/youtube_parser.py b/edmond/plugins/youtube_parser.py index 5dd1f26..7b51380 100644 --- a/edmond/plugins/youtube_parser.py +++ b/edmond/plugins/youtube_parser.py @@ -1,13 +1,16 @@ import re +from typing import cast, Optional try: - from googleapiclient.errors import Error as GoogleApiError + from googleapiclient.errors import Error as GoogleApiError # type: ignore DEPENDENCIES_FOUND = True except ImportError: DEPENDENCIES_FOUND = False from edmond.plugin import Plugin +from edmond.plugins.playlist_of_the_day import PlaylistOfTheDayPlugin +from edmond.plugins.youtube import YoutubePlugin class YoutubeParserPlugin(Plugin): @@ -20,14 +23,29 @@ class YoutubeParserPlugin(Plugin): def __init__(self, bot): super().__init__(bot) self.priority = -3 - self._youtube_plugin = None + self._youtube_plugin: Optional[YoutubePlugin] = None + self._playlist_of_the_day_plugin: Optional[ + PlaylistOfTheDayPlugin + ] = None @property - def youtube_plugin(self): + def youtube_plugin(self) -> Optional[YoutubePlugin]: if self._youtube_plugin is None: - self._youtube_plugin = self.bot.get_plugin("youtube") + self._youtube_plugin = cast( + YoutubePlugin, + self.bot.get_plugin("youtube"), + ) return self._youtube_plugin + @property + def playlist_of_the_day_plugin(self) -> Optional[PlaylistOfTheDayPlugin]: + if self._playlist_of_the_day_plugin is None: + self._playlist_of_the_day_plugin = cast( + PlaylistOfTheDayPlugin, + self.bot.get_plugin("playlistoftheday"), + ) + return self._playlist_of_the_day_plugin + def on_welcome(self, _): if not (self.youtube_plugin and self.youtube_plugin.is_ready): self.bot.log_w("Youtube plugin is not available.") @@ -40,14 +58,22 @@ class YoutubeParserPlugin(Plugin): for word in words: matched = self.VIDEO_URL_RE.match(word) if matched: - return self.handle_match(matched, event.target) + title = self.get_video_title(matched) + if title: + self.bot.say(event.target, title) + self.add_to_playlist(word, title) + return True + else: + self.signal_failure(event.target) return False - def handle_match(self, matched, target): + def get_video_title(self, matched) -> Optional[str]: + if self.youtube_plugin is None: + return None groupdict = matched.groupdict() code = groupdict.get("code1") or groupdict.get("code2") if not code: - return False + return None try: search_response = ( self.youtube_plugin.youtube.videos() @@ -55,15 +81,17 @@ class YoutubeParserPlugin(Plugin): .execute() ) except GoogleApiError: - self.signal_failure(target) - return False + return None title = "" for result in search_response.get("items", []): if result["kind"] == "youtube#video": title = result["snippet"]["title"] break else: - self.signal_failure(target) - return False - self.bot.say(target, title) - return True + return None + return title + + def add_to_playlist(self, url: str, title: str): + if self.playlist_of_the_day_plugin is None: + return + self.playlist_of_the_day_plugin.add_line(f"{url} {title}")