diff --git a/channel_util.py b/channel_util.py index fd073f6..fb90b8b 100644 --- a/channel_util.py +++ b/channel_util.py @@ -6,6 +6,7 @@ import slack_util # Useful channels GENERAL = "C0CFHPNEM" +RANDOM = "C0CFDQWUW" COMMAND_CENTER_ID = "GCR631LQ1" SLAVES_TO_THE_MACHINE_ID = "C9WUQBYNP" BOTZONE = "C3BF2MFKM" @@ -26,4 +27,5 @@ async def channel_check_callback(slack: SlackClient, msg: dict, match: Match) -> slack_util.reply(slack, msg, response) -channel_check_hook = slack_util.Hook(channel_check_callback, pattern=r"channel id\s*(.*)") +channel_check_hook = slack_util.Hook(channel_check_callback, + pattern=r"channel id\s*(.*)") diff --git a/identifier.py b/identifier.py index 92bbe82..9a8de64 100644 --- a/identifier.py +++ b/identifier.py @@ -136,7 +136,7 @@ def lookup_brother_userids(brother: scroll_util.Brother) -> List[str]: return result -identify_hook = slack_util.Hook(identify_callback, pattern=r"i am (.*)") +identify_hook = slack_util.Hook(identify_callback, pattern=r"my scroll is (.*)") identify_other_hook = slack_util.Hook(identify_other_callback, pattern=r"<@(.*)>\s+has scroll\s+(.*)") -check_hook = slack_util.Hook(check_callback, pattern=r"my scroll") -name_hook = slack_util.Hook(name_callback, pattern=r"my name") +check_hook = slack_util.Hook(check_callback, pattern=r"what is my scroll") +name_hook = slack_util.Hook(name_callback, pattern=r"what is my name") diff --git a/main.py b/main.py index 4740989..e6d4605 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ import asyncio -from typing import List, Any, Match, AsyncGenerator +from typing import List, Any, AsyncGenerator from slackclient import SlackClient # Obvious @@ -8,6 +8,7 @@ import identifier import job_nagger import job_signoff import management_commands +import periodicals import scroll_util import slack_util import slavestothemachine @@ -56,16 +57,14 @@ def main() -> None: help_callback = management_commands.list_hooks_callback_gen(wrap.hooks) wrap.add_hook(slack_util.Hook(help_callback, pattern=management_commands.bot_help_pattern)) - # Schedule? - async def do_later(slk: SlackClient, msg: dict, match: Match): - time = int(match.group(1)) - await asyncio.sleep(time) - slack_util.reply(slk, msg, "hello!") - - wrap.add_hook(slack_util.Hook(do_later, "pingme\s+(\d+)")) + # Add boozebot + wrap.add_passive(periodicals.ItsTenPM()) event_loop = asyncio.get_event_loop() - event_loop.run_until_complete(wrap.listen()) + message_handling = wrap.respond_messages() + passive_handling = wrap.run_passives() + both = asyncio.gather(message_handling, passive_handling) + event_loop.run_until_complete(both) class ClientWrapper(object): @@ -88,10 +87,26 @@ class ClientWrapper(object): # Hooks go regex -> callback on (slack, msg, match) self.hooks: List[slack_util.Hook] = [] + # Periodicals are just wrappers around an iterable, basically + self.passives: List[slack_util.Passive] = [] + + # Scheduled events handling + def add_passive(self, per: slack_util.Passive) -> None: + self.passives.append(per) + + async def run_passives(self) -> None: + # Make a task to repeatedly spawn each event + awaitables = [p.run(self.slack) for p in self.passives] + await asyncio.gather(*awaitables) + + # Message handling def add_hook(self, hook: slack_util.Hook) -> None: self.hooks.append(hook) - async def listen(self): + async def respond_messages(self) -> None: + """ + Asynchronous tasks that eternally reads and responds to messages. + """ async for _ in self.spool_tasks(): print("Handling a message...!") @@ -108,6 +123,7 @@ class ClientWrapper(object): # Strip garbage msg['text'] = msg['text'].strip() + print("Recv: \"{}\"".format(msg['text'])) # Handle debug if msg['text'][:6] == "DEBUG ": diff --git a/management_commands.py b/management_commands.py index a801f46..778848b 100644 --- a/management_commands.py +++ b/management_commands.py @@ -24,4 +24,6 @@ async def reboot_callback(slack: SlackClient, msg: dict, match: Match) -> None: # Make hooks bot_help_pattern = r"bot help" # Can't init this directly, as it relies on us knowing all other hooks. handle in main -reboot_hook = slack_util.Hook(reboot_callback, pattern=r"reboot", channel_whitelist=[channel_util.COMMAND_CENTER_ID]) +reboot_hook = slack_util.Hook(reboot_callback, + pattern=r"reboot", + channel_whitelist=[channel_util.COMMAND_CENTER_ID]) diff --git a/periodicals.py b/periodicals.py new file mode 100644 index 0000000..6812ce4 --- /dev/null +++ b/periodicals.py @@ -0,0 +1,30 @@ +import asyncio +from datetime import datetime + +from slackclient import SlackClient + +import channel_util +import slack_util + + +def seconds_until(target: datetime) -> float: + curr = datetime.now() + delta = target - curr + return delta.seconds + + +class ItsTenPM(slack_util.Passive): + async def run(self, slack: SlackClient) -> None: + while True: + # Get 10PM + ten_pm = datetime.now().replace(hour=21, minute=0, second=0) + + # Find out how long until it, then sleep that long + delay = seconds_until(ten_pm) + await asyncio.sleep(delay) + + # Crow like a rooster + slack_util.reply(slack, {}, "IT'S 10 PM!", in_thread=False, to_channel=channel_util.RANDOM) + + # Wait a while before trying it again, to prevent duplicates + await asyncio.sleep(60) diff --git a/scroll_util.py b/scroll_util.py index 830c0be..cd99a84 100644 --- a/scroll_util.py +++ b/scroll_util.py @@ -20,6 +20,9 @@ class Brother(object): self.name = name self.scroll = scroll + def __repr__(self): + return "".format(self.name, self.scroll) + # load the family tree familyfile = open("sortedfamilytree.txt", 'r') diff --git a/slack_util.py b/slack_util.py index d84dc08..95b8871 100644 --- a/slack_util.py +++ b/slack_util.py @@ -1,7 +1,7 @@ -import asyncio import re +from asyncio import Task from time import sleep -from typing import Any, Optional, Generator, Match, Callable, List, Coroutine +from typing import Any, Optional, Generator, Match, Callable, List, Coroutine, AsyncGenerator from slackclient import SlackClient @@ -70,14 +70,20 @@ def message_stream(slack: SlackClient) -> Generator[dict, None, None]: """ # Do forever while True: - if slack.rtm_connect(with_team_state=False, auto_reconnect=True): - print("Waiting for messages") - while True: - sleep(1) - update = slack.rtm_read() - for item in update: - if item.get('type') == 'message': - yield item + try: + if slack.rtm_connect(with_team_state=False, auto_reconnect=True): + print("Waiting for messages") + while True: + sleep(1) + update = slack.rtm_read() + for item in update: + if item.get('type') == 'message': + yield item + except OSError as e: + print("Error while reading messages:") + print(e) + except (ValueError, TypeError): + print("Malformed message... Restarting connection") sleep(5) print("Connection failed - retrying") @@ -90,7 +96,7 @@ Callback = Callable[[SlackClient, dict, Match], MsgAction] class Hook(object): def __init__(self, callback: Callback, - pattern: str = None, + pattern: str, channel_whitelist: Optional[List[str]] = None, channel_blacklist: Optional[List[str]] = None): # Save all @@ -100,9 +106,6 @@ class Hook(object): self.callback = callback # Remedy some sensible defaults - if self.pattern is None: - self.pattern = ".*" - if self.channel_blacklist is None: import channel_util self.channel_blacklist = [channel_util.GENERAL] @@ -135,3 +138,12 @@ class Hook(object): def invoke(self, slack: SlackClient, msg: dict, match: Match): return self.callback(slack, msg, match) + + +class Passive(object): + """ + Base class for Periodical tasks, such as reminders and stuff + """ + async def run(self, slack: SlackClient) -> None: + # Run this passive routed through the specified slack client. + raise NotImplementedError() diff --git a/slavestothemachine.py b/slavestothemachine.py index d7b3ac8..b4a9e5e 100644 --- a/slavestothemachine.py +++ b/slavestothemachine.py @@ -89,6 +89,9 @@ async def dump_work_callback(slack: SlackClient, msg: dict, match: Match) -> Non # Make dem HOOKs -count_work_hook = slack_util.Hook(count_work_callback, channel_whitelist=[channel_util.SLAVES_TO_THE_MACHINE_ID]) -dump_work_hook = slack_util.Hook(dump_work_callback, pattern="dump towel data", +count_work_hook = slack_util.Hook(count_work_callback, + pattern=".*", + channel_whitelist=[channel_util.SLAVES_TO_THE_MACHINE_ID]) +dump_work_hook = slack_util.Hook(dump_work_callback, + pattern="dump towel data", channel_whitelist=[channel_util.COMMAND_CENTER_ID])