From 589ada8f8d7dcc78921a09a848cfd84485423239 Mon Sep 17 00:00:00 2001 From: John Buluba Date: Mon, 1 Aug 2016 12:42:13 +0300 Subject: [PATCH] Adding a SleepSchedule worker. Pause for some time every day (#2193) * Added Sleeper worker * changed Sleep worker name to SleepSchedule * fixed wrong import names * changed name in log --- pokemongo_bot/cell_workers/__init__.py | 3 +- pokemongo_bot/cell_workers/sleep_schedule.py | 98 +++++++++++++++++ pokemongo_bot/test/sleep_schedule_test.py | 107 +++++++++++++++++++ 3 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 pokemongo_bot/cell_workers/sleep_schedule.py create mode 100644 pokemongo_bot/test/sleep_schedule_test.py diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index 2f5bee3fa5..f8b4afc261 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -15,4 +15,5 @@ from follow_spiral import FollowSpiral from collect_level_up_reward import CollectLevelUpReward from base_task import BaseTask -from follow_cluster import FollowCluster \ No newline at end of file +from follow_cluster import FollowCluster +from sleep_schedule import SleepSchedule \ No newline at end of file diff --git a/pokemongo_bot/cell_workers/sleep_schedule.py b/pokemongo_bot/cell_workers/sleep_schedule.py new file mode 100644 index 0000000000..221867f0e6 --- /dev/null +++ b/pokemongo_bot/cell_workers/sleep_schedule.py @@ -0,0 +1,98 @@ +from datetime import datetime, timedelta +from time import sleep +from random import uniform +from pokemongo_bot import logger +from pokemongo_bot.cell_workers.base_task import BaseTask + + +class SleepSchedule(BaseTask): + """Pauses the execution of the bot every day for some time + + Simulates the user going to sleep every day for some time, the sleep time + and the duration is changed every day by a random offset defined in the + config file + Example Config: + { + "type": "SleepSchedule", + "config": { + "time": "12:00", + "duration":"5:30", + "time_random_offset": "00:30", + "duration_random_offset": "00:30" + } + } + time: (HH:MM) local time that the bot should sleep + duration: (HH:MM) the duration of sleep + time_random_offset: (HH:MM) random offset of time that the sleep will start + for this example the possible start time is 11:30-12:30 + duration_random_offset: (HH:MM) random offset of duration of sleep + for this example the possible duration is 5:00-6:00 + """ + + LOG_INTERVAL_SECONDS = 600 + SCHEDULING_MARGIN = timedelta(minutes=10) # Skip if next sleep is RESCHEDULING_MARGIN from now + + def initialize(self): + # self.bot.event_manager.register_event('sleeper_scheduled', parameters=('datetime',)) + self._process_config() + self._schedule_next_sleep() + + def work(self): + if datetime.now() >= self._next_sleep: + self._sleep() + self._schedule_next_sleep() + self.bot.login() + + def _process_config(self): + self.time = datetime.strptime(self.config.get('time', '01:00'), '%H:%M') + + # Using datetime for easier stripping of timedeltas + duration = datetime.strptime(self.config.get('duration', '07:00'), '%H:%M') + self.duration = int(timedelta(hours=duration.hour, minutes=duration.minute).total_seconds()) + + time_random_offset = datetime.strptime(self.config.get('time_random_offset', '01:00'), '%H:%M') + self.time_random_offset = int( + timedelta( + hours=time_random_offset.hour, minutes=time_random_offset.minute).total_seconds()) + + duration_random_offset = datetime.strptime(self.config.get('duration_random_offset', '00:30'), '%H:%M') + self.duration_random_offset = int( + timedelta( + hours=duration_random_offset.hour, minutes=duration_random_offset.minute).total_seconds()) + + def _schedule_next_sleep(self): + self._next_sleep = self._get_next_sleep_schedule() + self._next_duration = self._get_next_duration() + logger.log('SleepSchedule: next sleep at {}'.format(str(self._next_sleep)), color='green') + + def _get_next_sleep_schedule(self): + now = datetime.now() + self.SCHEDULING_MARGIN + next_time = now.replace(hour=self.time.hour, minute=self.time.minute) + + next_time += timedelta(seconds=self._get_random_offset(self.time_random_offset)) + + # If sleep time is passed add one day + if next_time <= now: + next_time += timedelta(days=1) + + return next_time + + def _get_next_duration(self): + duration = self.duration + self._get_random_offset(self.duration_random_offset) + return duration + + def _get_random_offset(self, max_offset): + offset = uniform(-max_offset, max_offset) + return int(offset) + + def _sleep(self): + sleep_to_go = self._next_duration + logger.log('It\'s time for sleep.') + while sleep_to_go > 0: + logger.log('Sleeping for {} more seconds'.format(sleep_to_go), 'yellow') + if sleep_to_go < self.LOG_INTERVAL_SECONDS: + sleep(sleep_to_go) + sleep_to_go = 0 + else: + sleep(self.LOG_INTERVAL_SECONDS) + sleep_to_go -= self.LOG_INTERVAL_SECONDS diff --git a/pokemongo_bot/test/sleep_schedule_test.py b/pokemongo_bot/test/sleep_schedule_test.py new file mode 100644 index 0000000000..05c35afbb6 --- /dev/null +++ b/pokemongo_bot/test/sleep_schedule_test.py @@ -0,0 +1,107 @@ +import unittest +from datetime import timedelta, datetime +from mock import patch, MagicMock +from pokemongo_bot.cell_workers.sleep_schedule import SleepSchedule +from tests import FakeBot + + +class SleepScheculeTestCase(unittest.TestCase): + config = {'time': '12:20', 'duration': '01:05', 'time_random_offset': '00:05', 'duration_random_offset': '00:05'} + + def setUp(self): + self.bot = FakeBot() + self.worker = SleepSchedule(self.bot, self.config) + + def test_config(self): + self.assertEqual(self.worker.time.hour, 12) + self.assertEqual(self.worker.time.minute, 20) + self.assertEqual(self.worker.duration, timedelta(hours=1, minutes=5).total_seconds()) + self.assertEqual(self.worker.time_random_offset, timedelta(minutes=5).total_seconds()) + self.assertEqual(self.worker.duration_random_offset, timedelta(minutes=5).total_seconds()) + + @patch('pokemongo_bot.cell_workers.sleep_schedule.datetime') + def test_get_next_time(self, mock_datetime): + mock_datetime.now.return_value = datetime(year=2016, month=8, day=01, hour=8, minute=0) + + next_time = self.worker._get_next_sleep_schedule() + from_date = datetime(year=2016, month=8, day=1, hour=12, minute=15) + to_date = datetime(year=2016, month=8, day=1, hour=12, minute=25) + + self.assertGreaterEqual(next_time, from_date) + self.assertLessEqual(next_time, to_date) + + @patch('pokemongo_bot.cell_workers.sleep_schedule.datetime') + def test_get_next_time_called_near_activation_time(self, mock_datetime): + mock_datetime.now.return_value = datetime(year=2016, month=8, day=1, hour=12, minute=25) + + next = self.worker._get_next_sleep_schedule() + from_date = datetime(year=2016, month=8, day=02, hour=12, minute=15) + to_date = datetime(year=2016, month=8, day=02, hour=12, minute=25) + + self.assertGreaterEqual(next, from_date) + self.assertLessEqual(next, to_date) + + @patch('pokemongo_bot.cell_workers.sleep_schedule.datetime') + def test_get_next_time_called_when_this_days_time_passed(self, mock_datetime): + mock_datetime.now.return_value = datetime(year=2016, month=8, day=1, hour=14, minute=0) + + next = self.worker._get_next_sleep_schedule() + from_date = datetime(year=2016, month=8, day=02, hour=12, minute=15) + to_date = datetime(year=2016, month=8, day=02, hour=12, minute=25) + + self.assertGreaterEqual(next, from_date) + self.assertLessEqual(next, to_date) + + def test_get_next_duration(self): + from_seconds = int(timedelta(hours=1).total_seconds()) + to_seconds = int(timedelta(hours=1, minutes=10).total_seconds()) + + duration = self.worker._get_next_duration() + + self.assertGreaterEqual(duration, from_seconds) + self.assertLessEqual(duration, to_seconds) + + @patch('pokemongo_bot.cell_workers.sleep_schedule.sleep') + def test_sleep(self, mock_sleep): + self.worker._next_duration = SleepSchedule.LOG_INTERVAL_SECONDS * 10 + self.worker._sleep() + #Sleep should be called 10 times with LOG_INTERVAL_SECONDS as argument + self.assertEqual(mock_sleep.call_count, 10) + calls = [x[0][0] for x in mock_sleep.call_args_list] + for arg in calls: + self.assertEqual(arg, SleepSchedule.LOG_INTERVAL_SECONDS) + + @patch('pokemongo_bot.cell_workers.sleep_schedule.sleep') + def test_sleep_not_divedable_by_interval(self, mock_sleep): + self.worker._next_duration = SleepSchedule.LOG_INTERVAL_SECONDS * 10 + 5 + self.worker._sleep() + self.assertEqual(mock_sleep.call_count, 11) + + calls = [x[0][0] for x in mock_sleep.call_args_list] + for arg in calls[:-1]: + self.assertEqual(arg, SleepSchedule.LOG_INTERVAL_SECONDS) + #Last call must be 5 + self.assertEqual(calls[-1], 5) + + @patch('pokemongo_bot.cell_workers.sleep_schedule.sleep') + @patch('pokemongo_bot.cell_workers.sleep_schedule.datetime') + def test_call_work_before_schedule(self, mock_datetime, mock_sleep): + self.worker._next_sleep = datetime(year=2016, month=8, day=1, hour=12, minute=0) + mock_datetime.now.return_value = self.worker._next_sleep - timedelta(minutes=5) + + self.worker.work() + + self.assertEqual(mock_sleep.call_count, 0) + + @patch('pokemongo_bot.cell_workers.sleep_schedule.sleep') + @patch('pokemongo_bot.cell_workers.sleep_schedule.datetime') + def test_call_work_after_schedule(self, mock_datetime, mock_sleep): + self.bot.login = MagicMock() + self.worker._next_sleep = datetime(year=2016, month=8, day=1, hour=12, minute=0) + # Change time to be after schedule + mock_datetime.now.return_value = self.worker._next_sleep + timedelta(minutes=5) + + self.worker.work() + + self.assertGreater(mock_sleep.call_count, 0) + self.assertGreater(self.bot.login.call_count, 0)