From 494b9f45ad0ef3a29c48bcdbf61d7f6cc1f89cbb Mon Sep 17 00:00:00 2001 From: Cameron Ball Date: Tue, 27 Nov 2018 15:41:36 +0800 Subject: [PATCH] Implement reminders for house maintenance --- .gitignore | 1 + common.php | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++- purjolok.php | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ strings.php | 55 ++++++++++++++++++++++++++++++- taskMatrix.php | 47 +++++++++++++++++++++++++++ tasks.php | 61 ++++++++++++++++++++++++++++++++++ unfinished.php | 35 ++++++++++++++++++++ 7 files changed, 392 insertions(+), 2 deletions(-) create mode 100644 taskMatrix.php create mode 100644 tasks.php create mode 100644 unfinished.php diff --git a/.gitignore b/.gitignore index 65216c5..17d2f3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ config.php .ac-php-conf.json +tasks diff --git a/common.php b/common.php index 904c28a..1d996ef 100644 --- a/common.php +++ b/common.php @@ -18,6 +18,11 @@ function identity($x) { return $x; } +const notEmpty = 'notEmpty'; +function notEmpty($value) { + return !empty($value); +} + function getMessageSender($update) { return PARTICIPANT_IDS[$update->get('message')->get('from')->get('id')]; } @@ -36,6 +41,17 @@ function debug($whatever) { echo ''; } +function partition(int $numPartitions, $array) { + $partitionSize = (int)ceil(count($array) / $numPartitions); + + return + filter(notEmpty)( + map(function($p) use ($array, $partitionSize) { + return array_slice($array, $p*$partitionSize, $partitionSize); + })(range(0, $numPartitions-1)) + ); +} + function getInbox($inbox) { STATIC $inboxes; @@ -55,11 +71,12 @@ function getRules() { return $rules = $rules ?? require 'rules.php'; } +const getString = 'getString'; function getString($identifier, ...$vars) { STATIC $strings; $strings = $strings ?? require 'strings.php'; - return sprintf($strings[$identifier], ...$vars); + return isset($strings[$identifier]) ? sprintf($strings[$identifier], ...$vars) : "[[$identifier]]"; } function formatDate($date) { @@ -116,6 +133,16 @@ function unlines($lines) { return implode("\n", $lines); } +const ununlines = 'ununlines'; +function ununlines($lines) { + return implode("\n\n", $lines); +} + +const zipWith = 'zipWith'; +function zipWith(callable $zipper, array $a, array $b) { + return array_map($zipper, $a, $b); +} + function field($field) { return function($array) use ($field) { return $array[$field]; @@ -184,6 +211,71 @@ function ∪($a, $b) { return array_merge($a, $b); } +function getSeason($monthNum) { + return ['summer', 'autumn', 'winter', 'spring'][floor(($monthNum)%12/3)]; +} + +function getMonthName($monthNum) { + return ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'][$monthNum-1]; +} + +function isStartOfSeason($monthNum, $dayNum) { + return ($monthNum)%3 == 0 && isStartOfMonth($dayNum); +} + +function isStartOfMonth($dayNum) { + return $dayNum < 8; +} + +function getTasksForTheSeason($season, $taskMatrix) { + return array_unique( + array_reduce( + $taskMatrix['annualy'][$season], + function($c, $v) { + return array_merge( + $c, + array_reduce($v, function($c, $v) { + return array_merge($c, is_array($v) ? $v : [$v]); + }, []) + ); + }, + [] + ) + ); +} + +function getTasksForTheMonth($monthNum, $taskMatrix) { + return array_merge( + $taskMatrix['monthly'], + $monthNum % 6 == 0 ? $taskMatrix['biannualy'] : [], + $monthNum % 3 == 0 ? $taskMatrix['quadriannualy'] : [], + array_filter( + $taskMatrix['annualy'][getSeason($monthNum)][getMonthName($monthNum)], + function($v) { + return !is_array($v); + } + ) + ); +} + +// NB weeknum is 1-4 (the week of the month, not consistent with other things) +function getTasksForTheWeek($weekNum, $monthNum, $taskMatrix) { + return array_merge( + $weekNum % 2 == 0 ? $taskMatrix['bimonthly'] : [], + $taskMatrix['annualy'][getSeason($monthNum)][getMonthName($monthNum)]['weekly'] ?? [], + partition(4, getTasksForTheMonth($monthNum, $taskMatrix))[$weekNum] + ); +} + +function closest($n, $list) { + $a = array_filter($list, function($value) use ($n) { + return $value <= $n; + }); + + arsort($a); + return array_values($a)[0]; +} + function getMessagesFromInbox($inbox, array $rules, $unseenOnly = true) { return array_filter( array_map( diff --git a/purjolok.php b/purjolok.php index eeb2e6d..395f41d 100644 --- a/purjolok.php +++ b/purjolok.php @@ -118,4 +118,105 @@ getTelegram()->addCommand( ); +getTelegram()->addCommand( + new class extends Command { + protected $name = 'tasks'; + protected $description = 'List tasks for this week'; + + public function handle($arguments) { + $dt = new DateTimeImmutable(); + $directory = sprintf( + "tasks/%s/%s/%s", + $dt->format('Y'), + $dt->format('F'), + $dt->format('W') + ); + + $mondays = [ + (int)(new DateTimeImmutable('first monday of this month'))->format('d'), + (int)(new DateTimeImmutable('second monday of this month'))->format('d'), + (int)(new DateTimeImmutable('third monday of this month'))->format('d'), + (int)(new DateTimeImmutable('fourth monday of this month'))->format('d'), + ]; + $closestMonday = closest($dt->format('d'), $mondays); + + $tasksForTheWeek = getTasksForTheWeek( + array_search($closestMonday, $mondays), + $dt->format('m'), + require 'taskMatrix.php' + ); + + $completedTasksFile = "$directory" . "/completed.txt"; + $completedTasks = file_exists($completedTasksFile) ? lines(trim(file_get_contents($completedTasksFile))) : []; + + $stringAndCode = function($string) { + return getString($string) . " (" . $string . ")"; + }; + + $this->replyWithMessage([ + 'text' => ununlines([ + getString('tasksForTheWeek'), + unlines(map($stringAndCode)(array_diff($tasksForTheWeek, $completedTasks))) + ]) + ]); + } + } +); + +getTelegram()->addCommand( + new class extends Command { + protected $name = 'completetask'; + protected $description = 'Mark a task as completed'; + + public function handle($arguments) { + $dt = new DateTimeImmutable(); + $mondays = [ + (int)(new DateTimeImmutable('first monday of this month'))->format('d'), + (int)(new DateTimeImmutable('second monday of this month'))->format('d'), + (int)(new DateTimeImmutable('third monday of this month'))->format('d'), + (int)(new DateTimeImmutable('fourth monday of this month'))->format('d'), + ]; + $closestMonday = closest($dt->format('d'), $mondays); + + $directory = sprintf( + "tasks/%s/%s/%s", + $dt->format('Y'), + $dt->format('F'), + $dt->format('W') + ); + + $tasksForTheWeek = getTasksForTheWeek( + array_search($closestMonday, $mondays), + $dt->format('m'), + require 'taskMatrix.php' + ); + + $completedTasksFile = "$directory" . "/completed.txt"; + $completedTasks = file_exists($completedTasksFile) ? lines(trim(file_get_contents($completedTasksFile))) : []; + + if (!is_dir($directory)) { + mkdir($directory, 0777, true); + } + + if (in_array($arguments, $completedTasks)) { + $this->replyWithMessage(['text' => getString('taskAlreadyCompleted')]); + return; + } + + if (!in_array($arguments, $tasksForTheWeek)) { + $this->replyWithMessage(['text' => getString('unknownTask')]); + return; + } + + file_put_contents( + $completedTasksFile, + "$arguments\n", + FILE_APPEND + ); + + $this->replyWithMessage(['text' => getString('taskCompleted')]); + } + } +); + getTelegram()->commandsHandler(true); //must come afterwards because lolzer diff --git a/strings.php b/strings.php index 8f41824..a4e03c1 100644 --- a/strings.php +++ b/strings.php @@ -7,5 +7,58 @@ return [ 'drinksomewater' => '🚰 Consider drinking some water! 💦 #HydrationNation', 'goodnightWearShorts' => 'I\'m signing off for the night. Before I go; tomorrow it\'s going to be about %s when you get up and %s later in the day. So consider wearing summer clothes. ☀️☀️☀️', 'goodnightNormal' => 'I\'m signing off for the night. Before I go; tomorrow it\'s going to be about %s when you get up and %s later in the day. Jumper and trousers, maybe?', - 'goodnight' => 'Goodnight!' + 'goodnight' => 'Goodnight!', + + 'beginningOfSummer' => 'It\'s the start of summer! To get you prepared, here are the unique things that will happen this season (in addition to regular weekly(ish) shit)...', + 'beginningOfWinter' => 'It\'s the start of winter (at last, fuck summer). Heads up lads, here are the unique tasks that are wanting doing this season (in addition to regular junk)...', + + 'beginningOfDecember' => 'Welcome to December! Oh boy!', + 'beginningOfJanuary' => 'Welcome to January! Oh boy.', + + 'anywayHeresTheMonth' => 'Anyway, here\'s the normal start of the month breakdown...', + 'anywayHeresTheWeek' => 'Anyway, like normal here\'s some stuff I think you guys should do this week...', + 'finallyHeresTheWeek' => 'And finally... Like always, here\'s some stuff I think you guys should do this week:', + 'happyMonday' => 'Happy Monday! Here are some tasks I think should get nutted out this week:', + + 'thisMonthThereAre' => 'This month there are %d tasks that need completing (as well as the regular tasks). I\'ll try my best to space them out over the coming weeks. As a heads up, here they are:', + 'seasonalMeme' => 'Please also enjoy this seasonal meme I picked for you: %s', + 'tasksForTheWeek' => 'Here are the incomplete tasks for this week...', + 'taskAlreadyCompleted' => 'Hmm... It looks as though that task was already done this week.', + 'taskCompleted' => 'Marked as completed, nice one!', + 'unknownTask' => 'That doesn\'t look like a task I asked you to do this week... Use the listtasks command to see all the tasks.', + + 'rangehoodfilters' => 'Clean the rangehood filters in the kitchen', + 'poolwater' => 'Check the level of the pool, and fill if needed', + 'weeds' => 'Inspect pavers for weeds (front and back), and spray/pull out as needed', + 'ants' => 'Inspect the pavers for ant hills and apply sand/back fill as needed', + 'leaves' => 'Sweep up leaves in the front yard', + 'cobwebs' => 'Inspect the exterior of the house for cobwebs (in the windows and shit)', + 'fireextinguisher' => 'Inspect fire extinguisher', + 'mowlawn' => 'Mow the lawn', + 'vacuum' => 'Vacuum the house', + 'mop' => 'Mop the tiles', + 'kitchen' => 'Clean the kitchen (wipe all surfaces/cupboards and clean the stove)', + 'cleanhouse' => 'Do a big cleanout of the house', + 'cleangarage' => 'Do a big cleanout of the garage', + 'testreliefevalve' => 'Test the relief valves on the water heater', + 'testsmokealarms' => 'Test smoke alarms', + 'smokealarmbatteries' => 'Replace smoke alarm batteries', + 'fridgecoils' => 'Clean the fridge coils', + 'chlorinatorplates' => 'Inspect and descale chlorinator if needed', + 'treeflowers' => 'Clean up the flowers from that stupid tree next door', + 'bulkgreens' => 'Prepare for bulk greens collection', + 'windowdebris' => 'Clean junk out of windows (inside and outside)', + 'cleangutters' => 'Clean out gutters to prepare for rain', + 'checkdrains' => 'Check down pipe drains for blockages', + 'coverpool' => 'Cover the pool', + 'cleanchimeny' => 'Clean soot out of the chimney', + 'tightenfixings' => 'Inspect and tighten (if needed) any fixings', + 'fanfilters' => 'Clean extractor fan filters (bathroom and air conditioner)', + 'bulkrubbish' => 'Prepare for bulk rubbish collection', + 'caulking' => 'Inspect and repair caulking', + 'showerheads' => 'Inspect and descale showerheads and taps', + 'shrubs' => 'Prune shrubs', + 'palmtrees' => 'Remove dead branches and seeds from palm trees', + 'uncoverpool' => 'Uncover the pool', + 'checktaps' => 'Inspect taps for leaks and repair as needed' ]; diff --git a/taskMatrix.php b/taskMatrix.php new file mode 100644 index 0000000..408e845 --- /dev/null +++ b/taskMatrix.php @@ -0,0 +1,47 @@ + ['rangehoodfilters', 'poolwater', 'weeds', 'ants', 'leaves', 'cobwebs', 'fireextinguisher', 'mowlawn'], + 'bimonthly' => ['vacuum', 'mop', 'kitchen'], + 'biannualy' => ['cleanhouse', 'cleangarage', 'testreliefevalve', 'smokealarmstest', 'smokealarmbatteries', 'fridgecoils'], + 'quadriannualy' => ['chlorinatorplates'], + 'annualy' => [ + 'summer' => [ + 'december' => [ + 'weekly' => ['treeflowers'] + ], + 'january' => [ + 'bulkgreens', + 'weekly' => ['treeflowers'] + ], + 'february' => [] + ], + 'autumn' => [ + 'march' => ['windowdebris'], + 'april' => ['cleangutters', 'checkdrains'], + 'may' => ['coverpool', 'cleanchimney'] + ], + 'winter' => [ + 'june' => ['tightenfixings', 'fanfilters'], + 'july' => ['bulkrubbish'], + 'august' => ['caulking', 'showerheads'] + ], + 'spring' => [ + 'september' => ['shrubs'], + 'october' => ['palmtrees', 'uncoverpool'], + 'november' => ['checktaps'] + ] + ] +]; + +/* + clean computer files + clean car + vacuum bedroom + change bed sheets + clean bathroom + dust bathroom + clean shower +*/ diff --git a/tasks.php b/tasks.php new file mode 100644 index 0000000..b03ff5a --- /dev/null +++ b/tasks.php @@ -0,0 +1,61 @@ +format('d'), + (int)(new DateTimeImmutable('second monday of this month'))->format('d'), + (int)(new DateTimeImmutable('third monday of this month'))->format('d'), + (int)(new DateTimeImmutable('fourth monday of this month'))->format('d'), +]; +$currentMonth = (new DateTimeImmutable())->format('m'); +$currentDayOfMonth = closest((new DateTimeImmutable())->format('d'), $mondays); +$currentWeekOfMonth = array_search($currentDayOfMonth, $mondays); + +$taskLists = array_merge( + isStartOfSeason($currentMonth, $currentDayOfMonth) ? [unlines(map(getString)(getTasksForTheSeason(getSeason($currentMonth), $taskMatrix)))] : [], + isStartOfMonth($currentDayOfMonth) ? [unlines(map(getString)(getTasksForTheMonth($currentMonth, $taskMatrix)))] : [], + [unlines(map(getString)(getTasksForTheWeek($currentWeekOfMonth, $currentMonth, $taskMatrix)))] +); + +$taskMessages = [ + [getString('happyMonday')], + [ + [getString('beginningOf'. ucfirst(getMonthName($currentMonth))), getString('thisMonthThereAre', count(getTasksForTheMonth($currentMonth, $taskMatrix)))], + getString('anywayHeresTheWeek') + ], + [ + getString('beginningOf' . ucfirst(getSeason($currentMonth))), + [getString('anywayHeresTheMonth'), getString('thisMonthThereAre', count(getTasksForTheMonth($currentMonth, $taskMatrix)))], + getString('finallyHeresTheWeek') + ] +]; + +$messages = zipWith( + function($message, $list) { + return ununlines( + array_merge( + is_array($message) ? $message : [$message], + [$list] + ) + ); + }, + // Magic. startOfSeason implis startofMonth so we get 2, start of month without start of season gives 1 and + // a regular week (not the start of a season or month) gives 0. And this is how the indicies are ordered in the array. + $taskMessages[isStartOfSeason($currentMonth, $currentDayOfMonth) + isStartOfMonth($currentDayOfMonth)], + $taskLists +); + +foreach ($messages as $message) { + sendToGroupChat($message); + sleep(rand(2,4)); +} + +$seasonalMemes = [ + 'summer' => ['https://www.youtube.com/watch?v=NqktmrKB3ko'] +]; + +if (isStartOfSeason($currentMonth, $currentDayOfMonth)) { + sendToGroupChat(getString('seasonalMeme', $seasonalMemes[getSeason($currentMonth)][array_rand($seasonalMemes[getSeason($currentMonth)])])); +} diff --git a/unfinished.php b/unfinished.php new file mode 100644 index 0000000..e637ba9 --- /dev/null +++ b/unfinished.php @@ -0,0 +1,35 @@ +format('d'), + (int)(new DateTimeImmutable('second monday of this month'))->format('d'), + (int)(new DateTimeImmutable('third monday of this month'))->format('d'), + (int)(new DateTimeImmutable('fourth monday of this month'))->format('d'), +]; + +$directory = sprintf( + "tasks/%s/%s/%s", + $dt->format('Y'), + $dt->format('F'), + $dt->format('W') +); +$completedTasksFile = "$directory" . "/completed.txt"; +$completedTasks = file_exists($completedTasksFile) ? lines(trim(file_get_contents($completedTasksFile))) : []; + +$closestMonday = closest($dt->format('d'), $mondays); + +$tasksForTheWeek = getTasksForTheWeek( + array_search($closestMonday, $mondays), + $dt->format('m'), + require 'taskMatrix.php' +); + +$unfinished = array_diff($tasksForTheWeek, $completedTasks); + +print_r( + array_diff(['treeflowers', 'someothershit'], ['treeflowers']) +); -- 2.11.0