From fe33a5a0e4d81ec4f73d556ec694a3030c0a5ebc Mon Sep 17 00:00:00 2001 From: Jacob Henry Date: Wed, 20 Feb 2019 20:46:58 -0500 Subject: [PATCH] Things are less hacky now. WIP --- channel_util.py | 4 +- client_wrapper.py | 2 +- identifier.py | 8 ++-- job_commands.py | 46 +++++++++++----------- main.py | 4 +- management_commands.py | 8 ++-- scroll_util.py | 2 +- slack_util.py | 87 +++++++++++++++++++++++++++++++++++------- slavestothemachine.py | 8 ++-- 9 files changed, 115 insertions(+), 54 deletions(-) diff --git a/channel_util.py b/channel_util.py index 620a2da..6afdda6 100644 --- a/channel_util.py +++ b/channel_util.py @@ -27,5 +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, - patterns=r"channel id\s*(.*)") +channel_check_hook = slack_util.ChannelHook(channel_check_callback, + patterns=r"channel id\s*(.*)") diff --git a/client_wrapper.py b/client_wrapper.py index 13af720..f4d8bb8 100644 --- a/client_wrapper.py +++ b/client_wrapper.py @@ -123,5 +123,5 @@ class ClientWrapper(object): _singleton = ClientWrapper() -def get_client_wrapper() -> ClientWrapper: +def grab() -> ClientWrapper: return _singleton diff --git a/identifier.py b/identifier.py index 93d8300..ad2c6b3 100644 --- a/identifier.py +++ b/identifier.py @@ -138,7 +138,7 @@ def lookup_brother_userids(brother: scroll_util.Brother) -> List[str]: return result -identify_hook = slack_util.Hook(identify_callback, patterns=r"my scroll is (.*)") -identify_other_hook = slack_util.Hook(identify_other_callback, patterns=r"<@(.*)>\s+has scroll\s+(.*)") -check_hook = slack_util.Hook(check_callback, patterns=r"what is my scroll") -name_hook = slack_util.Hook(name_callback, patterns=r"what is my name") +identify_hook = slack_util.ChannelHook(identify_callback, patterns=r"my scroll is (.*)") +identify_other_hook = slack_util.ChannelHook(identify_other_callback, patterns=r"<@(.*)>\s+has scroll\s+(.*)") +check_hook = slack_util.ChannelHook(check_callback, patterns=r"what is my scroll") +name_hook = slack_util.ChannelHook(name_callback, patterns=r"what is my name") diff --git a/job_commands.py b/job_commands.py index 878e735..5b38d15 100644 --- a/job_commands.py +++ b/job_commands.py @@ -149,7 +149,7 @@ async def _mod_jobs(slack: SlackClient, new_hook = slack_util.ReplyWaiter(foc, pattern, msg["ts"], 120) # Register it - client_wrapper.get_client_wrapper().add_hook(new_hook) + client_wrapper.grab().add_hook(new_hook) async def signoff_callback(slack: SlackClient, msg: dict, match: Match) -> None: @@ -351,50 +351,50 @@ async def nag_callback(slack, msg, match): slack_util.reply(slack, msg, response, in_thread=False, to_channel=channel_util.GENERAL) -signoff_hook = slack_util.Hook(signoff_callback, - patterns=[ +signoff_hook = slack_util.ChannelHook(signoff_callback, + patterns=[ r"signoff\s+(.*)", r"sign off\s+(.*)", ], - channel_whitelist=[channel_util.HOUSEJOBS]) + channel_whitelist=[channel_util.HOUSEJOBS]) -undo_hook = slack_util.Hook(undo_callback, - patterns=[ +undo_hook = slack_util.ChannelHook(undo_callback, + patterns=[ r"unsignoff\s+(.*)", r"undosignoff\s+(.*)", r"undo signoff\s+(.*)", ], - channel_whitelist=[channel_util.HOUSEJOBS]) + channel_whitelist=[channel_util.HOUSEJOBS]) -late_hook = slack_util.Hook(late_callback, - patterns=[ +late_hook = slack_util.ChannelHook(late_callback, + patterns=[ r"marklate\s+(.*)", r"mark late\s+(.*)", ], - channel_whitelist=[channel_util.HOUSEJOBS]) + channel_whitelist=[channel_util.HOUSEJOBS]) -reset_hook = slack_util.Hook(reset_callback, - patterns=[ +reset_hook = slack_util.ChannelHook(reset_callback, + patterns=[ r"reset signoffs", r"reset sign offs", ], - channel_whitelist=[channel_util.COMMAND_CENTER_ID]) + channel_whitelist=[channel_util.COMMAND_CENTER_ID]) -nag_hook = slack_util.Hook(nag_callback, - patterns=[ +nag_hook = slack_util.ChannelHook(nag_callback, + patterns=[ r"nagjobs\s+(.*)", r"nag jobs\s+(.*)" ], - channel_whitelist=[channel_util.COMMAND_CENTER_ID]) + channel_whitelist=[channel_util.COMMAND_CENTER_ID]) -reassign_hook = slack_util.Hook(reassign_callback, - patterns=r"reassign\s+(.*?)->\s+(.+)", - channel_whitelist=[channel_util.HOUSEJOBS]) +reassign_hook = slack_util.ChannelHook(reassign_callback, + patterns=r"reassign\s+(.*?)->\s+(.+)", + channel_whitelist=[channel_util.HOUSEJOBS]) -refresh_hook = slack_util.Hook(refresh_callback, - patterns=[ +refresh_hook = slack_util.ChannelHook(refresh_callback, + patterns=[ "refresh points", "update points" ], - channel_whitelist=[channel_util.COMMAND_CENTER_ID] - ) + channel_whitelist=[channel_util.COMMAND_CENTER_ID] + ) diff --git a/main.py b/main.py index 7ccad9f..de141f9 100644 --- a/main.py +++ b/main.py @@ -16,7 +16,7 @@ import slavestothemachine def main() -> None: - wrap = client_wrapper.get_client_wrapper() + wrap = client_wrapper.grab() # Add scroll handling wrap.add_hook(scroll_util.scroll_hook) @@ -46,7 +46,7 @@ def main() -> None: wrap.add_hook(job_commands.refresh_hook) # Add help - wrap.add_hook(slack_util.Hook(help_callback, patterns=[r"help", r"bot\s+help"])) + wrap.add_hook(slack_util.ChannelHook(help_callback, patterns=[r"help", r"bot\s+help"])) # Add boozebot # wrap.add_passive(periodicals.ItsTenPM()) diff --git a/management_commands.py b/management_commands.py index d1ca512..5f8c0f5 100644 --- a/management_commands.py +++ b/management_commands.py @@ -6,7 +6,7 @@ import channel_util import slack_util -def list_hooks_callback_gen(hooks: List[slack_util.Hook]) -> slack_util.Callback: +def list_hooks_callback_gen(hooks: List[slack_util.ChannelHook]) -> slack_util.Callback: # noinspection PyUnusedLocal async def callback(slack, msg, match): slack_util.reply(slack, msg, "\n".join(hook.patterns for hook in hooks)) @@ -23,6 +23,6 @@ async def reboot_callback(slack: SlackClient, msg: dict, match: Match) -> None: # Make hooks -reboot_hook = slack_util.Hook(reboot_callback, - patterns=r"reboot", - channel_whitelist=[channel_util.COMMAND_CENTER_ID]) +reboot_hook = slack_util.ChannelHook(reboot_callback, + patterns=r"reboot", + channel_whitelist=[channel_util.COMMAND_CENTER_ID]) diff --git a/scroll_util.py b/scroll_util.py index 7de85d6..19d5c07 100644 --- a/scroll_util.py +++ b/scroll_util.py @@ -105,4 +105,4 @@ async def find_by_name(name: str, threshold: Optional[float] = None) -> Brother: raise BrotherNotFound(msg) -scroll_hook = slack_util.Hook(scroll_callback, patterns=r"scroll\s+(.*)") +scroll_hook = slack_util.ChannelHook(scroll_callback, patterns=r"scroll\s+(.*)") diff --git a/slack_util.py b/slack_util.py index 6de0ba0..ee1d0a6 100644 --- a/slack_util.py +++ b/slack_util.py @@ -1,4 +1,6 @@ +from __future__ import annotations import re +from dataclasses import dataclass from time import sleep, time from typing import Any, Optional, Generator, Match, Callable, List, Coroutine, Union, TypeVar, Awaitable @@ -10,9 +12,10 @@ Slack helpers. Separated for compartmentalization """ -def reply(slack: SlackClient, msg: dict, text: str, in_thread: bool = True, to_channel: str = None) -> dict: +def reply(msg: dict, text: str, in_thread: bool = True, to_channel: str = None) -> dict: """ - Sends message with "text" as its content to the channel that message came from + Sends message with "text" as its content to the channel that message came from. + Returns the JSON response. """ # If no channel specified, just do same as msg if to_channel is None: @@ -27,9 +30,10 @@ def reply(slack: SlackClient, msg: dict, text: str, in_thread: bool = True, to_c return send_message(slack, text, to_channel) -def send_message(slack: SlackClient, text: str, channel: str, thread: str = None, broadcast: bool = False) -> dict: +def send_message(text: str, channel: str, thread: str = None, broadcast: bool = False) -> dict: """ - Copy of the internal send message function of slack + Copy of the internal send message function of slack, with some helpful options. + Returns the JSON response. """ kwargs = {"channel": channel, "text": text} if thread: @@ -40,7 +44,58 @@ def send_message(slack: SlackClient, text: str, channel: str, thread: str = None return slack.api_call("chat.postMessage", **kwargs) -def message_stream(slack: SlackClient) -> Generator[dict, None, None]: +""" +Objects to represent things +""" +class User: + pass + +class Channel: + @property + def channel_name(self) -> str: + raise NotImplementedError() + + + + +""" +Below we have a modular system that represents possible event contents. +""" +@dataclass +class Event: + channel: Optional[ChannelContext] + user: Optional[UserContext] + message: Optional[Message] + + +# If this was posted in a specific channel or conversation +@dataclass +class ChannelContext: + channel_id: str + + +# If there is a specific user associated with this event +@dataclass +class UserContext: + user_id: str + + def as_user(self) -> User: + raise NotImplementedError() + + +# Whether or not this is a threadable text message +@dataclass +class Message: + ts: str + text: str + + +@dataclass +class File: + pass + + +def message_stream(slack: SlackClient) -> Generator[Event, None, None]: """ Generator that yields messages from slack. Messages are in standard api format, look it up. @@ -83,35 +138,41 @@ class VerboseWrapper(Callable): try: return await awt except Exception as e: - reply(self.slack, self.command_msg, "Error: {}".format(str(e)), True) + reply(self.command_msg, "Error: {}".format(str(e)), True) raise e +# The result of a message MsgAction = Coroutine[Any, Any, None] -Callback = Callable[[SlackClient, dict, Match], MsgAction] +# The function called on a message +Callback = Callable[[SlackClient, Message, Match], MsgAction] class DeadHook(Exception): pass +# Abstract hook parent class class AbsHook(object): def __init__(self, consumes_applicable: bool): # Whether or not messages that yield a coroutine should not be checked further self.consumes = consumes_applicable - def try_apply(self, slack: SlackClient, msg: dict) -> Optional[Coroutine[None, None, None]]: + def try_apply(self, event: Event) -> Optional[MsgAction]: raise NotImplementedError() -class Hook(AbsHook): +class ChannelHook(AbsHook): + """ + Hook that handles messages in a variety of channels + """ def __init__(self, callback: Callback, patterns: Union[str, List[str]], channel_whitelist: Optional[List[str]] = None, channel_blacklist: Optional[List[str]] = None, consumer: bool = True): - super(Hook, self).__init__(consumer) + super(ChannelHook, self).__init__(consumer) # Save all if not isinstance(patterns, list): @@ -131,7 +192,7 @@ class Hook(AbsHook): else: raise ValueError("Cannot whitelist and blacklist") - def try_apply(self, slack: SlackClient, msg: dict) -> Optional[Coroutine[None, None, None]]: + def try_apply(self, event: Event) -> Optional[MsgAction]: """ Returns whether a message should be handled by this dict, returning a Match if so, or None """ @@ -170,7 +231,7 @@ class ReplyWaiter(AbsHook): self.start_time = time() self.dead = False - def try_apply(self, slack: SlackClient, msg: dict) -> Optional[Coroutine[None, None, None]]: + def try_apply(self, event: Event) -> Optional[MsgAction]: # First check: are we dead of age yet? time_alive = time() - self.start_time should_expire = time_alive > self.lifetime @@ -199,6 +260,6 @@ class Passive(object): Base class for Periodical tasks, such as reminders and stuff """ - async def run(self, slack: SlackClient) -> None: + async def run(self) -> None: # Run this passive routed through the specified slack client. raise NotImplementedError() diff --git a/slavestothemachine.py b/slavestothemachine.py index 691ec4e..b3d0935 100644 --- a/slavestothemachine.py +++ b/slavestothemachine.py @@ -89,7 +89,7 @@ async def record_towel_contribution(for_brother: Brother, contribution_count: in # Make dem HOOKs -count_work_hook = slack_util.Hook(count_work_callback, - patterns=".*", - channel_whitelist=[channel_util.SLAVES_TO_THE_MACHINE_ID], - consumer=False) +count_work_hook = slack_util.ChannelHook(count_work_callback, + patterns=".*", + channel_whitelist=[channel_util.SLAVES_TO_THE_MACHINE_ID], + consumer=False)