From 091d0ecd721f727fc0abad21821deda11568b7a0 Mon Sep 17 00:00:00 2001 From: dece Date: Sun, 1 Nov 2020 19:27:12 +0100 Subject: [PATCH] sleep: add plugin --- config.json.example | 8 ++++ edmond/bot.py | 5 ++ edmond/plugins/sleep.py | 75 ++++++++++++++++++++++++++++++ edmond/plugins/tests/__init__.py | 0 edmond/plugins/tests/test_sleep.py | 47 +++++++++++++++++++ edmond/tests/__init__.py | 0 edmond/tests/test_plugin.py | 6 +++ 7 files changed, 141 insertions(+) create mode 100644 edmond/plugins/sleep.py create mode 100644 edmond/plugins/tests/__init__.py create mode 100644 edmond/plugins/tests/test_sleep.py create mode 100644 edmond/tests/__init__.py create mode 100644 edmond/tests/test_plugin.py diff --git a/config.json.example b/config.json.example index 252cf5d..47c6da1 100644 --- a/config.json.example +++ b/config.json.example @@ -50,6 +50,14 @@ "commands": ["choose"], "separator": "or" }, + "sleep": { + "sleep_time": 23, + "wakeup_time": 7, + "snore": "Zzzz", + "sleep_message": "/me falls asleep", + "wakeup_message": "/me wakes up", + "snore_rate": 1.0 + }, "wikipedia": { "commands": ["science", "define"], "lang": "en", diff --git a/edmond/bot.py b/edmond/bot.py index 287d578..1455f8a 100644 --- a/edmond/bot.py +++ b/edmond/bot.py @@ -93,6 +93,11 @@ class Bot(irc.client.SimpleIRCClient, Logger): self.log_d(f"Private message from {nick} to {target}: {message}") self.run_plugin_callbacks(event) + def on_ping(self, connection, event): + """Handle a ping; can be used as a random event timer.""" + self.log_d(f"Received ping from {event.target}.") + self.run_plugin_callbacks(event) + def run(self): """Connect the bot to server, join channels and start responding.""" self.log_i("Starting Edmond.") diff --git a/edmond/plugins/sleep.py b/edmond/plugins/sleep.py new file mode 100644 index 0000000..2611e28 --- /dev/null +++ b/edmond/plugins/sleep.py @@ -0,0 +1,75 @@ +from datetime import datetime, timedelta + +from edmond.plugin import Plugin +from edmond.utils import proc + + +class SleepPlugin(Plugin): + """Handle sleep state of the bot, snore a little bit.""" + + REQUIRED_CONFIGS = [ + "sleep_time", "wakeup_time", "snore", "sleep_message", "wakeup_message", + "snore_rate" + ] + + def __init__(self, bot): + super().__init__(bot) + + def on_ping(self, event): + awake = self.get_runtime_value("awake") + self.bot.log_w(f"{self.bot.channels}") + in_sleep_hours = self.is_sleep_time(datetime.now()) + if awake and in_sleep_hours: + self.fall_asleep() + elif not awake: + if not in_sleep_hours: + self.wake_up() + else: + # Maybe snore. + if proc(self.config["snore_rate"]): + for channel in self.bot.channels: + self.bot.say(channel, self.config["snore"]) + + def fall_asleep(self): + for channel in self.bot.channels: + self.bot.say(channel, self.config["sleep_message"]) + self.set_runtime_value("awake", False) + + def wake_up(self): + self.set_runtime_value("awake", True) + for channel in self.bot.channels: + self.bot.say(channel, self.config["wakeup_message"]) + + def is_sleep_time(self, now): + """Return True if the bot should be sleeping by now. + + The sleep range can span over 2 days (e.g. 23 to 7) or be contained in a + day (e.g. 0 to 8). + """ + sleep_time = self.config["sleep_time"] + wakeup_time = self.config["wakeup_time"] + current_hour = now.hour + # If we're before the sleep hour, check that we are not in the + # previous sleep range. Else just check today's range. + if current_hour < sleep_time: + ref_dt = now - timedelta(days=1) + else: + ref_dt = now + sleep_dt, wakeup_dt = self._get_sleep_range( + ref_dt, + sleep_time, + wakeup_time + ) + return sleep_dt <= now < wakeup_dt + + @staticmethod + def _get_sleep_range(now, sleep_time, wakeup_time): + """Return the (start, end) datetime tuple using config values. + + Properly accounts for a sleep range going around midnight. + """ + sleep_dt = now.replace(hour=sleep_time, minute=0, second=0) + wakeup_dt = now.replace(hour=wakeup_time, minute=0, second=0) + if wakeup_time < sleep_time: + wakeup_dt += timedelta(days=1) + return sleep_dt, wakeup_dt diff --git a/edmond/plugins/tests/__init__.py b/edmond/plugins/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/edmond/plugins/tests/test_sleep.py b/edmond/plugins/tests/test_sleep.py new file mode 100644 index 0000000..1ea309c --- /dev/null +++ b/edmond/plugins/tests/test_sleep.py @@ -0,0 +1,47 @@ +import unittest +from datetime import datetime, timedelta +from unittest.mock import Mock, patch + +from ..sleep import SleepPlugin +from edmond.tests.test_plugin import get_plugin_patcher + + +class TestSleepPlugin(unittest.TestCase): + + def test_is_sleep_time(self): + with get_plugin_patcher(SleepPlugin): + plugin = SleepPlugin() + today = datetime.today() + tomorrow = datetime.today() + timedelta(days=1) + + # Given sleep time from 23:00 to 7:00 + plugin.config = {"sleep_time": 23, "wakeup_time": 7} + # You should sleep within the sleep range. + today_23_00 = today.replace(hour=23, minute=0, second=0) + self.assertTrue(plugin.is_sleep_time(today_23_00)) + today_23_30 = today.replace(hour=23, minute=30, second=0) + self.assertTrue(plugin.is_sleep_time(today_23_30)) + tomorrow_00_00 = tomorrow.replace(hour=0, minute=0, second=0) + self.assertTrue(plugin.is_sleep_time(tomorrow_00_00)) + tomorrow_06_59 = tomorrow.replace(hour=6, minute=59, second=0) + self.assertTrue(plugin.is_sleep_time(tomorrow_06_59)) + # But not outside the range. + today_12_00 = today.replace(hour=12, minute=0, second=0) + self.assertFalse(plugin.is_sleep_time(today_12_00)) + today_22_59 = today.replace(hour=22, minute=59, second=0) + self.assertFalse(plugin.is_sleep_time(today_22_59)) + + # Given sleep time from 00:00 to 7:00 + plugin.config = {"sleep_time": 0, "wakeup_time": 7} + # You should sleep within the sleep range. + today_00_00 = today.replace(hour=0, minute=0, second=0) + self.assertTrue(plugin.is_sleep_time(today_00_00)) + today_06_59 = today.replace(hour=6, minute=59, second=0) + self.assertTrue(plugin.is_sleep_time(today_06_59)) + tomorrow_06_59 = tomorrow.replace(hour=6, minute=59, second=0) + self.assertTrue(plugin.is_sleep_time(tomorrow_06_59)) + # But not outside the range. + today_12_00 = today.replace(hour=12, minute=0, second=0) + self.assertFalse(plugin.is_sleep_time(today_12_00)) + today_22_59 = today.replace(hour=22, minute=59, second=0) + self.assertFalse(plugin.is_sleep_time(today_22_59)) diff --git a/edmond/tests/__init__.py b/edmond/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/edmond/tests/test_plugin.py b/edmond/tests/test_plugin.py new file mode 100644 index 0000000..64c6028 --- /dev/null +++ b/edmond/tests/test_plugin.py @@ -0,0 +1,6 @@ +from unittest.mock import patch + + +def get_plugin_patcher(plugin_class): + """Return a context manager to patch a Plugin init.""" + return patch.object(plugin_class, "__init__", lambda bot: None)