From 79210d5636577d3958cdea4a25a7a274b2bda028 Mon Sep 17 00:00:00 2001 From: Laanan <59519006+Laanan@users.noreply.github.com> Date: Mon, 11 Jan 2021 19:29:33 -0800 Subject: [PATCH 1/2] Add files via upload --- snoozebot.py | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 snoozebot.py diff --git a/snoozebot.py b/snoozebot.py new file mode 100644 index 000000000..b8ef17785 --- /dev/null +++ b/snoozebot.py @@ -0,0 +1,119 @@ +from typing import Any, Dict +import re +from datetime import datetime +from crontab import CronTab +import os +import subprocess + + +# Current plan is to have the user send snoozebot a message +# containing the desired interval for a reminder (like 4 days). +# Then snoozebot sets a cron job. Cron then calls a python script, +# ping.py with parameters for the send_message function in the Rest +# API. This is a POST https: request to POST https://yourZulipDomain.zulipchat.com/api/v1/messages +# I am thinking the datetime module might be useful for calculating the required +# inputs for the chron job. + + + + +#Todos: +# 1) Figure out what the incoming web hook needs to be to have +# snoozebot send the reminder. +# 2) figure out how to have snoozebot start the cron job +# 3) I'm thinking I am going to need a function in snoozebot +# to handle the incoming web hook from crontab + + + + + +class SnoozeBotHandler: + + + def usage(self) -> str: + return ''' + Snoozebot is a reminder tool that will message you at your requested time in the future. Currently, Snoozebot + accepts the format '# time', as in '2 minutes.' With this format, Snoozebot is using the 'now +# time' syntax + for the At command. If that means nothing to you, no worries, here is an example of what you would type in a + zulip reply: @**snoozebot** 4 days + What that means is, snoozebot, message/mention me in this thread 4 days from this moment. + In the future, specific date + time formatting will be implemented, such as: @**snoozebot** 4:13PM on 12/01/2027. + If you have any questions or feedback, please PM me on Zulip Community Chat. -- MLH + ''' + + def handle_message(self, message: Dict[str, Any], bot_handler: Any) -> None: + pattern1 = re.compile(r'(\b\d+\s(minute|hour|day|week|month|year))', re.IGNORECASE) + # hopefully this pattern object will work for any iteration of "snooze for x days/weeks/months...will have to test. + #pattern1 = re.compile('\b\d+\s(minute|hour|day|week|month|year)[s]?', re.IGNORECASE) + pattern2 = re.compile(r'(\b\d{1,2}:\d{2}\s?(AM|PM|am|pm)\s?(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday|tomorrow))', re.IGNORECASE) + #pattern3 will be specific date + time + # Changing the match variables to use the group method instead of the findall method to get around the tuple issue + #match = pattern1.findall(message['content']) + #match2 = pattern2.findall(message['content']) + match = re.search(pattern1, message['content']) + match2 = re.search(pattern2, message['content']) + stream_id = message['stream_id'] + topic = message['subject'] + user = message['sender_full_name'] + heredoc = f"""END + zulip-send --stream '{stream_id}' --subject '{topic}' --message 'Ping! @**{user}**' --config-file ~/python-zulip-api/zulip_bots/zulip_bots/bots/snoozebot/zuliprc + END""" + + # Setting up At command (pipe method) + # zulip_send_command = zulip-send %(stream name) %(message name) %(message) + # at_command = " %(zulip_send) | at %(time_input)" + + + #setting up the crontab variables here, but realized that At is the right tool, not crontab + #cron = CronTab(user='root') + + + # message handling + if message['content'] == '': + bot_response = "Please specify the **snooze interval**, such as '4 days'. For help, message me with 'help.'" + bot_handler.send_reply(message, bot_response) + elif message['content'] == 'help': + bot_handler.send_reply(message, self.usage()) + else: + if pattern1.search(message['content']) != None: + + x = match.group(1) + #y = str(x[0]) + z = "Ok, I'll remind you in " + x + #print(message['content']) + #print(pattern1) + # print(message['stream_id']) + #print(x[0]) + #print(y) + #print(match) + #print(match[0]) + #print(pattern1.search(message[content]) + #y.join(x) + print(x) + print(heredoc) + bot_handler.send_reply(message, z) + # This is where the At job will be called. + os.system("at now +%s << %s" %(x, heredoc)) + # Decided to use At instead of cron. keeping just in case + # job = cron.new(command='echo hello_word') + # job.minute.every(5) + # cron.write() + emoji_name = 'alarm clock' + bot_handler.react(message, emoji_name) + elif pattern2.search(message['content']) != None: + # print(pattern2.search(message['content']) + #print(message['content']) + l = match2.group(1) + #m = str(l[0]) + n = "Ok, I'll remind you at " + l + bot_handler.send_reply(message, n) + os.system("at %s << %s" %(l, heredoc)) + + else: + bot_handler.send_reply(message, "Oops, that won't work. Message me with 'help' for help on how to use me.") + return + +handler_class = SnoozeBotHandler + + From 7c54d08f7db45ee5fd1fde34728305c079de3192 Mon Sep 17 00:00:00 2001 From: Laanan <59519006+Laanan@users.noreply.github.com> Date: Sat, 23 Jan 2021 12:26:06 -0800 Subject: [PATCH 2/2] Update snoozebot.py Completed script. --- snoozebot.py | 171 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 106 insertions(+), 65 deletions(-) diff --git a/snoozebot.py b/snoozebot.py index b8ef17785..29b729a23 100644 --- a/snoozebot.py +++ b/snoozebot.py @@ -1,75 +1,114 @@ from typing import Any, Dict import re -from datetime import datetime -from crontab import CronTab import os import subprocess -# Current plan is to have the user send snoozebot a message -# containing the desired interval for a reminder (like 4 days). -# Then snoozebot sets a cron job. Cron then calls a python script, -# ping.py with parameters for the send_message function in the Rest -# API. This is a POST https: request to POST https://yourZulipDomain.zulipchat.com/api/v1/messages -# I am thinking the datetime module might be useful for calculating the required -# inputs for the chron job. +class SnoozeBotHandler: + def usage(self) -> str: + return ''' + Snoozebot is a reminder tool that will message you at your requested time in the future. Snoozebot + accepts three formats to set reminders. -#Todos: -# 1) Figure out what the incoming web hook needs to be to have -# snoozebot send the reminder. -# 2) figure out how to have snoozebot start the cron job -# 3) I'm thinking I am going to need a function in snoozebot -# to handle the incoming web hook from crontab + FORMAT: + 1) '# time' + Example: '2 minutes.' + With this format, Snoozebot is using the 'now +# time' syntax + for the At command. If that means nothing to you, no worries, here is an example of what you would type in a + zulip reply: @**snoozebot** 4 days. Minutes, days, weeks, months, and years are all acceptable for the time + variable in this format. + 2) 'time day' + Example: '@**snoozebot** 8:00AM Monday.' + The days of the week, as well as 'tomorrow' and 'today' are acceptable, as in '@**snoozebot** 10:00PM today.' -class SnoozeBotHandler: + 3) 'time month day' + Example: '10:00am Jul 31' + This format will set a reminder at a specific date and time. Each month is abbreviated to three characters. - def usage(self) -> str: - return ''' - Snoozebot is a reminder tool that will message you at your requested time in the future. Currently, Snoozebot - accepts the format '# time', as in '2 minutes.' With this format, Snoozebot is using the 'now +# time' syntax - for the At command. If that means nothing to you, no worries, here is an example of what you would type in a - zulip reply: @**snoozebot** 4 days - What that means is, snoozebot, message/mention me in this thread 4 days from this moment. - In the future, specific date + time formatting will be implemented, such as: @**snoozebot** 4:13PM on 12/01/2027. - If you have any questions or feedback, please PM me on Zulip Community Chat. -- MLH + MESSAGE LOCATION: + + 1) Message Snoozebot in the topic you want to be reminded of. + + If you message Snoozebot from a topic, Snoozebot + will mention you in a PM at the set time. The message will be 'Ping! @**USER** #**stream>topic**. You may + click the link to stream/topic link to return to the referenced topic. + + 2) Directly PM Snoozebot + + In this case, the reminder will not automatically contain a link, however,Snoozebot is set to echo back the + whole message you originally wrote. That way, if you want to be reminded of a specific topic, you can simply + copy and paste the zulip link for the topic into your message, and Snoozebot will regurgitate it back to you + for your later use and reference. Example: @**snoozebot** Remind me of 10:00am Aug 1. Here, the + PM you get on August 1st will be: 'Ping! @**USER** Remind me of 10:00am Aug 1.' + + NOTES: + + * Snoozebot will always immediately echo back to you "Ok, I'll remind you..." as a confirmation that the reminder + was set. If this does not happen, Snoozebot probably crashed. + * There is no way to unset the reminder. + * Time must be ##:## -- the regex patter expects a colon. + * The reminder always comes as a PM to prevent spamming other users with your own reminders. + + + If you have any questions or feedback, please PM me on Zulip Community Chat. -- MLH ''' def handle_message(self, message: Dict[str, Any], bot_handler: Any) -> None: - pattern1 = re.compile(r'(\b\d+\s(minute|hour|day|week|month|year))', re.IGNORECASE) - # hopefully this pattern object will work for any iteration of "snooze for x days/weeks/months...will have to test. - #pattern1 = re.compile('\b\d+\s(minute|hour|day|week|month|year)[s]?', re.IGNORECASE) - pattern2 = re.compile(r'(\b\d{1,2}:\d{2}\s?(AM|PM|am|pm)\s?(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday|tomorrow))', re.IGNORECASE) - #pattern3 will be specific date + time - # Changing the match variables to use the group method instead of the findall method to get around the tuple issue - #match = pattern1.findall(message['content']) - #match2 = pattern2.findall(message['content']) + + # Setting up the regex patterns + pattern1 = re.compile(r'(\b\d+\s(minute|hour|day|week|month|year)s?)', re.IGNORECASE) + pattern2 = re.compile(r'(\b\d{1,2}:\d{2}\s?(AM|PM|am|pm)\s?(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday|tomorrow|today))', re.IG + NORECASE) + pattern3 = re.compile(r'(\d{1,2}:\d{1,2}\s?(AM|PM)\s?(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s?\d{1,2})', re.IGNORECASE) + + # Setting up the Match variables) match = re.search(pattern1, message['content']) match2 = re.search(pattern2, message['content']) - stream_id = message['stream_id'] - topic = message['subject'] - user = message['sender_full_name'] - heredoc = f"""END - zulip-send --stream '{stream_id}' --subject '{topic}' --message 'Ping! @**{user}**' --config-file ~/python-zulip-api/zulip_bots/zulip_bots/bots/snoozebot/zuliprc + match3 = re.search(pattern3, message['content']) + + # Message related variables + + if message.get('stream_id'): + stream_id = message.get('stream_id') + stream_name = message.get('display_recipient') + content = message['content'] + topic = message['subject'] + user = message['sender_full_name'] + sender = message['sender_email'] + heredoc = f"""END + zulip-send {sender} --message 'Ping! @**{user}** #**{stream_name}>{topic}**' --config-file ~/python-zulip-api/zulip_bots/zulip_bot + s/bots/snoozebot/zuliprc END""" - - # Setting up At command (pipe method) + + + else: + stream_id = None + stream_name = None + content = message['content'] + topic = message['subject'] + user = message['sender_full_name'] + sender = message['sender_email'] + heredoc = f"""END + zulip-send {sender} --message 'Ping! @**{user}** {content}' --config-file ~/python-zulip-api/zulip_bots/zulip_bots/bots/snoozebot/ + zuliprc + END""" + + + # Example of etting up At command with the pipe method instead... # zulip_send_command = zulip-send %(stream name) %(message name) %(message) # at_command = " %(zulip_send) | at %(time_input)" - - - #setting up the crontab variables here, but realized that At is the right tool, not crontab - #cron = CronTab(user='root') - - - # message handling + + + + # Message handling if message['content'] == '': bot_response = "Please specify the **snooze interval**, such as '4 days'. For help, message me with 'help.'" bot_handler.send_reply(message, bot_response) @@ -77,43 +116,45 @@ def handle_message(self, message: Dict[str, Any], bot_handler: Any) -> None: bot_handler.send_reply(message, self.usage()) else: if pattern1.search(message['content']) != None: - + x = match.group(1) - #y = str(x[0]) z = "Ok, I'll remind you in " + x + + # Diagnostic print messages #print(message['content']) - #print(pattern1) - # print(message['stream_id']) + #print(message['stream_id']) #print(x[0]) #print(y) #print(match) #print(match[0]) #print(pattern1.search(message[content]) #y.join(x) - print(x) + print(message) print(heredoc) + + # Snoozebot echos back to user bot_handler.send_reply(message, z) + # This is where the At job will be called. - os.system("at now +%s << %s" %(x, heredoc)) - # Decided to use At instead of cron. keeping just in case - # job = cron.new(command='echo hello_word') - # job.minute.every(5) - # cron.write() + os.system("at now +%s << %s" %(x, heredoc)) emoji_name = 'alarm clock' bot_handler.react(message, emoji_name) + elif pattern2.search(message['content']) != None: - # print(pattern2.search(message['content']) - #print(message['content']) + l = match2.group(1) - #m = str(l[0]) n = "Ok, I'll remind you at " + l bot_handler.send_reply(message, n) - os.system("at %s << %s" %(l, heredoc)) + os.system("at %s << %s" %(l, heredoc)) + + elif pattern3.search(message['content']) != None: - else: + l = match3.group(1) + n = "Ok, I'll remind you at " + l + bot_handler.send_reply(message, n) + os.system("at %s << %s" %(l, heredoc)) + else: bot_handler.send_reply(message, "Oops, that won't work. Message me with 'help' for help on how to use me.") - return + return handler_class = SnoozeBotHandler - -