Things are less hacky now. WIP

This commit is contained in:
Jacob Henry 2019-02-20 20:46:58 -05:00
parent b35593b89d
commit fe33a5a0e4
9 changed files with 115 additions and 54 deletions

View File

@ -27,5 +27,5 @@ async def channel_check_callback(slack: SlackClient, msg: dict, match: Match) ->
slack_util.reply(slack, msg, response) slack_util.reply(slack, msg, response)
channel_check_hook = slack_util.Hook(channel_check_callback, channel_check_hook = slack_util.ChannelHook(channel_check_callback,
patterns=r"channel id\s*(.*)") patterns=r"channel id\s*(.*)")

View File

@ -123,5 +123,5 @@ class ClientWrapper(object):
_singleton = ClientWrapper() _singleton = ClientWrapper()
def get_client_wrapper() -> ClientWrapper: def grab() -> ClientWrapper:
return _singleton return _singleton

View File

@ -138,7 +138,7 @@ def lookup_brother_userids(brother: scroll_util.Brother) -> List[str]:
return result return result
identify_hook = slack_util.Hook(identify_callback, patterns=r"my scroll is (.*)") identify_hook = slack_util.ChannelHook(identify_callback, patterns=r"my scroll is (.*)")
identify_other_hook = slack_util.Hook(identify_other_callback, patterns=r"<@(.*)>\s+has scroll\s+(.*)") identify_other_hook = slack_util.ChannelHook(identify_other_callback, patterns=r"<@(.*)>\s+has scroll\s+(.*)")
check_hook = slack_util.Hook(check_callback, patterns=r"what is my scroll") check_hook = slack_util.ChannelHook(check_callback, patterns=r"what is my scroll")
name_hook = slack_util.Hook(name_callback, patterns=r"what is my name") name_hook = slack_util.ChannelHook(name_callback, patterns=r"what is my name")

View File

@ -149,7 +149,7 @@ async def _mod_jobs(slack: SlackClient,
new_hook = slack_util.ReplyWaiter(foc, pattern, msg["ts"], 120) new_hook = slack_util.ReplyWaiter(foc, pattern, msg["ts"], 120)
# Register it # 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: async def signoff_callback(slack: SlackClient, msg: dict, match: Match) -> None:
@ -351,14 +351,14 @@ async def nag_callback(slack, msg, match):
slack_util.reply(slack, msg, response, in_thread=False, to_channel=channel_util.GENERAL) slack_util.reply(slack, msg, response, in_thread=False, to_channel=channel_util.GENERAL)
signoff_hook = slack_util.Hook(signoff_callback, signoff_hook = slack_util.ChannelHook(signoff_callback,
patterns=[ patterns=[
r"signoff\s+(.*)", r"signoff\s+(.*)",
r"sign off\s+(.*)", r"sign off\s+(.*)",
], ],
channel_whitelist=[channel_util.HOUSEJOBS]) channel_whitelist=[channel_util.HOUSEJOBS])
undo_hook = slack_util.Hook(undo_callback, undo_hook = slack_util.ChannelHook(undo_callback,
patterns=[ patterns=[
r"unsignoff\s+(.*)", r"unsignoff\s+(.*)",
r"undosignoff\s+(.*)", r"undosignoff\s+(.*)",
@ -366,32 +366,32 @@ undo_hook = slack_util.Hook(undo_callback,
], ],
channel_whitelist=[channel_util.HOUSEJOBS]) channel_whitelist=[channel_util.HOUSEJOBS])
late_hook = slack_util.Hook(late_callback, late_hook = slack_util.ChannelHook(late_callback,
patterns=[ patterns=[
r"marklate\s+(.*)", r"marklate\s+(.*)",
r"mark late\s+(.*)", r"mark late\s+(.*)",
], ],
channel_whitelist=[channel_util.HOUSEJOBS]) channel_whitelist=[channel_util.HOUSEJOBS])
reset_hook = slack_util.Hook(reset_callback, reset_hook = slack_util.ChannelHook(reset_callback,
patterns=[ patterns=[
r"reset signoffs", r"reset signoffs",
r"reset sign offs", 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, nag_hook = slack_util.ChannelHook(nag_callback,
patterns=[ patterns=[
r"nagjobs\s+(.*)", r"nagjobs\s+(.*)",
r"nag jobs\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, reassign_hook = slack_util.ChannelHook(reassign_callback,
patterns=r"reassign\s+(.*?)-&gt;\s+(.+)", patterns=r"reassign\s+(.*?)-&gt;\s+(.+)",
channel_whitelist=[channel_util.HOUSEJOBS]) channel_whitelist=[channel_util.HOUSEJOBS])
refresh_hook = slack_util.Hook(refresh_callback, refresh_hook = slack_util.ChannelHook(refresh_callback,
patterns=[ patterns=[
"refresh points", "refresh points",
"update points" "update points"

View File

@ -16,7 +16,7 @@ import slavestothemachine
def main() -> None: def main() -> None:
wrap = client_wrapper.get_client_wrapper() wrap = client_wrapper.grab()
# Add scroll handling # Add scroll handling
wrap.add_hook(scroll_util.scroll_hook) wrap.add_hook(scroll_util.scroll_hook)
@ -46,7 +46,7 @@ def main() -> None:
wrap.add_hook(job_commands.refresh_hook) wrap.add_hook(job_commands.refresh_hook)
# Add help # 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 # Add boozebot
# wrap.add_passive(periodicals.ItsTenPM()) # wrap.add_passive(periodicals.ItsTenPM())

View File

@ -6,7 +6,7 @@ import channel_util
import slack_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 # noinspection PyUnusedLocal
async def callback(slack, msg, match): async def callback(slack, msg, match):
slack_util.reply(slack, msg, "\n".join(hook.patterns for hook in hooks)) 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 # Make hooks
reboot_hook = slack_util.Hook(reboot_callback, reboot_hook = slack_util.ChannelHook(reboot_callback,
patterns=r"reboot", patterns=r"reboot",
channel_whitelist=[channel_util.COMMAND_CENTER_ID]) channel_whitelist=[channel_util.COMMAND_CENTER_ID])

View File

@ -105,4 +105,4 @@ async def find_by_name(name: str, threshold: Optional[float] = None) -> Brother:
raise BrotherNotFound(msg) 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+(.*)")

View File

@ -1,4 +1,6 @@
from __future__ import annotations
import re import re
from dataclasses import dataclass
from time import sleep, time from time import sleep, time
from typing import Any, Optional, Generator, Match, Callable, List, Coroutine, Union, TypeVar, Awaitable 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 no channel specified, just do same as msg
if to_channel is None: 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) 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} kwargs = {"channel": channel, "text": text}
if thread: 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) 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. Generator that yields messages from slack.
Messages are in standard api format, look it up. Messages are in standard api format, look it up.
@ -83,35 +138,41 @@ class VerboseWrapper(Callable):
try: try:
return await awt return await awt
except Exception as e: 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 raise e
# The result of a message
MsgAction = Coroutine[Any, Any, None] 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): class DeadHook(Exception):
pass pass
# Abstract hook parent class
class AbsHook(object): class AbsHook(object):
def __init__(self, consumes_applicable: bool): def __init__(self, consumes_applicable: bool):
# Whether or not messages that yield a coroutine should not be checked further # Whether or not messages that yield a coroutine should not be checked further
self.consumes = consumes_applicable 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() raise NotImplementedError()
class Hook(AbsHook): class ChannelHook(AbsHook):
"""
Hook that handles messages in a variety of channels
"""
def __init__(self, def __init__(self,
callback: Callback, callback: Callback,
patterns: Union[str, List[str]], patterns: Union[str, List[str]],
channel_whitelist: Optional[List[str]] = None, channel_whitelist: Optional[List[str]] = None,
channel_blacklist: Optional[List[str]] = None, channel_blacklist: Optional[List[str]] = None,
consumer: bool = True): consumer: bool = True):
super(Hook, self).__init__(consumer) super(ChannelHook, self).__init__(consumer)
# Save all # Save all
if not isinstance(patterns, list): if not isinstance(patterns, list):
@ -131,7 +192,7 @@ class Hook(AbsHook):
else: else:
raise ValueError("Cannot whitelist and blacklist") 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 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.start_time = time()
self.dead = False 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? # First check: are we dead of age yet?
time_alive = time() - self.start_time time_alive = time() - self.start_time
should_expire = time_alive > self.lifetime should_expire = time_alive > self.lifetime
@ -199,6 +260,6 @@ class Passive(object):
Base class for Periodical tasks, such as reminders and stuff 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. # Run this passive routed through the specified slack client.
raise NotImplementedError() raise NotImplementedError()

View File

@ -89,7 +89,7 @@ async def record_towel_contribution(for_brother: Brother, contribution_count: in
# Make dem HOOKs # Make dem HOOKs
count_work_hook = slack_util.Hook(count_work_callback, count_work_hook = slack_util.ChannelHook(count_work_callback,
patterns=".*", patterns=".*",
channel_whitelist=[channel_util.SLAVES_TO_THE_MACHINE_ID], channel_whitelist=[channel_util.SLAVES_TO_THE_MACHINE_ID],
consumer=False) consumer=False)