Added reply callback procedure?

This commit is contained in:
Jacob Henry 2018-11-12 13:27:46 -05:00
parent 716ec52a98
commit 1ddfa7403b
5 changed files with 80 additions and 17 deletions

View File

@ -70,6 +70,7 @@ class ClientWrapper(object):
# Strip garbage # Strip garbage
msg['text'] = msg['text'].strip() msg['text'] = msg['text'].strip()
print("Recv: \"{}\"".format(msg['text'])) print("Recv: \"{}\"".format(msg['text']))
print(msg)
# Msg is good # Msg is good
# Find which hook, if any, satisfies # Find which hook, if any, satisfies

View File

@ -36,6 +36,9 @@ class Job(object):
# Extra stuff, interpreted # Extra stuff, interpreted
day: Optional[date] day: Optional[date]
def pretty_fmt(self) -> str:
return "{} - {} at {}".format(self.name, self.day_of_week, self.house)
@dataclass @dataclass
class JobAssignment(object): class JobAssignment(object):
@ -53,7 +56,6 @@ class JobAssignment(object):
late = "y" if self.late else "n" late = "y" if self.late else "n"
return self.job.name, self.job.house, self.job.day_of_week, self.assignee.name, signer_name, late return self.job.name, self.job.house, self.job.day_of_week, self.assignee.name, signer_name, late
@dataclass @dataclass
class PointStatus(object): class PointStatus(object):
""" """

View File

@ -63,11 +63,9 @@ def do_signoff(slack: SlackClient, msg: dict, on_assign_index: int, by_brother:
house_management.export_points(headers, points) house_management.export_points(headers, points)
# Then we respond cool! # Then we respond cool!
slack_util.reply(slack, msg, "{} signed off {} for {} {} {}".format(on_assign.signer.name, slack_util.reply(slack, msg, "{} signed off {} for {}".format(on_assign.signer.name,
on_assign.assignee.name, on_assign.assignee.name,
on_assign.job.house, on_assign.job.pretty_fmt()))
on_assign.job.day_of_week,
on_assign.job.name))
alert_user(slack, on_assign.assignee, "Your house job was signed off") alert_user(slack, on_assign.assignee, "Your house job was signed off")
@ -108,7 +106,6 @@ async def signoff_callback(slack: SlackClient, msg: dict, match: Match) -> None:
if len(closest_assigns) == 0: if len(closest_assigns) == 0:
slack_util.reply(slack, msg, "Unable to find any jobs assigned to brother {} " slack_util.reply(slack, msg, "Unable to find any jobs assigned to brother {} "
"(identified as {}).".format(signee_name, signee.name)) "(identified as {}).".format(signee_name, signee.name))
return
# If theres only one job, sign it off # If theres only one job, sign it off
elif len(closest_assigns) == 1: elif len(closest_assigns) == 1:
@ -118,12 +115,37 @@ async def signoff_callback(slack: SlackClient, msg: dict, match: Match) -> None:
targ_assign_index = assigns.index(targ_assign) targ_assign_index = assigns.index(targ_assign)
do_signoff(slack, msg, targ_assign_index, signer) do_signoff(slack, msg, targ_assign_index, signer)
return
# If theres multiple jobs, we need to get a follow up! # If theres multiple jobs, we need to get a follow up!
else: else:
slack_util.reply(slack, msg, "Dunno how to handle multiple jobs yet") # Say we need more info
return job_list = "\n".join("{}: {}".format(i, a.job.pretty_fmt()) for i, a in enumerate(closest_assigns))
slack_util.reply(slack, msg, "Multiple sign off options dectected for brother {}.\n"
"Please enter the number corresponding to the job you wish to "
"sign off:\n{}\nIf you do not respond within 60 seconds, the signoff will "
"expire.".format(signee_name, job_list))
# Establish a follow up command pattern
pattern = r"\d+"
# Make the follow up callback
async def foc(_slack: SlackClient, _msg: dict, _match: Match) -> None:
# Get the number out
index = int(_match.group(0))
# Check that its valid
if 0 <= index < len(closest_assigns):
# We now know what we're trying to sign off!
specific_targ_assign = closest_assigns[index]
specific_targ_assign_index = assigns.index(specific_targ_assign)
do_signoff(_slack, _msg, specific_targ_assign_index, signer)
else:
# They gave a bad index, or we were unable to find the assignment again.
slack_util.reply(_slack, _msg, "Invalid job index / job unable to be found. Start over from the "
"signoff step.")
# Make a listener hook
slack_util.ReplyWaiter(foc, pattern, msg["ts"], 60)
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
@ -191,12 +213,12 @@ async def nag_callback(slack, msg, match):
signoff_hook = slack_util.Hook(signoff_callback, signoff_hook = slack_util.Hook(signoff_callback,
pattern=r"testsignoff\s+(.*)", pattern=r"signoff\s+(.*)",
channel_whitelist=[channel_util.HOUSEJOBS]) channel_whitelist=[channel_util.HOUSEJOBS])
reset_hook = slack_util.Hook(reset_callback, reset_hook = slack_util.Hook(reset_callback,
pattern=r"testreset signoffs", pattern=r"reset signoffs",
channel_whitelist=[channel_util.COMMAND_CENTER_ID]) # COMMAND_CENTER_ID channel_whitelist=[channel_util.COMMAND_CENTER_ID])
nag_hook = slack_util.Hook(nag_callback, nag_hook = slack_util.Hook(nag_callback,
pattern=r"nagjobs\s*(.*)", pattern=r"nagjobs\s*(.*)",

View File

@ -35,6 +35,7 @@ brothers_matches = [m for m in brothers_matches if m]
brothers: List[Brother] = [Brother(m.group(2), int(m.group(1))) for m in brothers_matches] brothers: List[Brother] = [Brother(m.group(2), int(m.group(1))) for m in brothers_matches]
recent_brothers: List[Brother] = brothers[700:] recent_brothers: List[Brother] = brothers[700:]
async def scroll_callback(slack: SlackClient, msg: dict, match: Match) -> None: async def scroll_callback(slack: SlackClient, msg: dict, match: Match) -> None:
""" """
Finds the scroll of a brother, or the brother of a scroll, based on msg text. Finds the scroll of a brother, or the brother of a scroll, based on msg text.
@ -54,7 +55,7 @@ async def scroll_callback(slack: SlackClient, msg: dict, match: Match) -> None:
result = "Couldn't find brother {}".format(query) result = "Couldn't find brother {}".format(query)
# Respond # Respond
slack_util.reply(slack, msg, result) print(slack_util.reply(slack, msg, result))
def find_by_scroll(scroll: int) -> Optional[Brother]: def find_by_scroll(scroll: int) -> Optional[Brother]:
@ -82,7 +83,7 @@ class BadName(Exception):
"match ratio {}. Please type name better.".format(self.name, self.score, self.threshold) "match ratio {}. Please type name better.".format(self.name, self.score, self.threshold)
def find_by_name(name: str, threshold: Optional[float] = None, recent_only: bool=False) -> Brother: def find_by_name(name: str, threshold: Optional[float] = None, recent_only: bool = False) -> Brother:
""" """
Looks up a brother by name. Raises exception if threshold provided and not met. Looks up a brother by name. Raises exception if threshold provided and not met.
@ -110,4 +111,4 @@ def find_by_name(name: str, threshold: Optional[float] = None, recent_only: bool
raise BadName(found, score, threshold) raise BadName(found, score, threshold)
scroll_hook = slack_util.Hook(scroll_callback, pattern=r"scroll\s+(.*)") scroll_hook = slack_util.Hook(scroll_callback, pattern=r"testscroll\s+(.*)")

View File

@ -1,5 +1,5 @@
import re import re
from time import sleep from time import sleep, time
from typing import Any, Optional, Generator, Match, Callable, List, Coroutine from typing import Any, Optional, Generator, Match, Callable, List, Coroutine
from slackclient import SlackClient from slackclient import SlackClient
@ -141,10 +141,47 @@ class Hook(AbsHook):
return self.callback(slack, msg, match) return self.callback(slack, msg, match)
class ReplyWaiter(AbsHook):
"""
A special hook that only cares about replies to a given message.
"""
def __init__(self, callback: Callback, pattern: str, thread_ts: str, lifetime: float):
super().__init__(True)
self.callback = callback
self.pattern = pattern
self.thread_ts = thread_ts
self.lifetime = lifetime
self.start_time = time()
self.dead = False
def try_apply(self, slack: SlackClient, msg: dict) -> Optional[Coroutine[None, None, None]]:
# First check: are we dead of age yet?
time_alive = time() - self.start_time
should_expire = time_alive < self.lifetime
# If so, give up the ghost
if self.dead or should_expire:
raise DeadHook()
# Otherwise proceed normally
# Is the msg the one we care about? If not, ignore
if msg.get("thread_ts", None) != self.thread_ts:
return None
# Does it match the regex? if not, ignore
match = re.match(self.pattern, msg['text'], flags=re.IGNORECASE)
if match:
return self.callback(slack, msg, match)
else:
return None
class Passive(object): 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, slack: SlackClient) -> None:
# Run this passive routed through the specified slack client. # Run this passive routed through the specified slack client.
raise NotImplementedError() raise NotImplementedError()