diff --git a/plugins/job_commands.py b/plugins/job_commands.py index b6724a9..b449ef3 100644 --- a/plugins/job_commands.py +++ b/plugins/job_commands.py @@ -1,428 +1,428 @@ -import logging -from dataclasses import dataclass -from typing import List, Match, Callable, TypeVar, Optional, Iterable, Any, Coroutine - -from fuzzywuzzy import fuzz - -import hooks -from plugins import identifier, house_management, scroll_util -import client -import slack_util - -SHEET_ID = "1f9p4H7TWPm8rAM4v_qr2Vc6lBiFNEmR-quTY9UtxEBI" - -MIN_RATIO = 80.0 - - -async def alert_user(brother: scroll_util.Brother, saywhat: str) -> None: - """ - DM a brother saying something. Wrapper around several simpler methods - """ - # We do this as a for loop just in case multiple people reg. to same scroll for some reason (e.g. dup accounts) - succ = False - for slack_id in await identifier.lookup_brother_userids(brother): - client.get_slack().send_message(saywhat, slack_id) - succ = True - - # Warn if we never find - if not succ: - logging.warning("Unable to find dm conversation for brother {}".format(brother)) - - -# Generic type -T = TypeVar("T") - - -def tiemax(items: Iterable[T], key: Callable[[T], Optional[float]]) -> List[T]: - best = [] - best_score = None - for elt in items: - # Compute the score - score = key(elt) - - # Ignore blank scores - if score is None: - continue - # Check if its the new best, wiping old if so - elif best_score is None or score > best_score: - best_score = score - best = [elt] - # Check if its same as last best - elif score == best_score: - best.append(elt) - return best - - -@dataclass -class _ModJobContext: - signer: scroll_util.Brother # The brother invoking the command - assign: house_management.JobAssignment # The job assignment to modify - - -async def _mod_jobs(event: slack_util.Event, - relevance_scorer: Callable[[house_management.JobAssignment], Optional[float]], - modifier: Callable[[_ModJobContext], Coroutine[Any, Any, None]], - no_job_msg: str = None - ) -> None: - """ - Stub function that handles various tasks relating to modifying jobs - :param relevance_scorer: Function scores job assignments on relevance. Determines which gets modified - :param modifier: Callback function to modify a job. Only called on a successful operation, and only on one job - """ - # Make an error wrapper - verb = slack_util.VerboseWrapper(event) - - # Who invoked this command? - signer = await verb(event.user.as_user().get_brother()) - - # Get all of the assignments - assigns = await verb(house_management.import_assignments()) - - # Find closest assignment to what we're after. This just wraps relevance_scorer to handle nones. - def none_scorer(a: Optional[house_management.JobAssignment]) -> Optional[float]: - if a is None: - return None - else: - return relevance_scorer(a) - - closest_assigns = tiemax(assigns, key=none_scorer) - - # This is what we do on success. It will or won't be called immediately based on what's in closest_assigns - async def success_callback(targ_assign: house_management.JobAssignment) -> None: - # First get the most up to date version of the jobs - fresh_assigns = await verb(house_management.import_assignments()) - - # Find the one that matches what we had before - fresh_targ_assign = fresh_assigns[fresh_assigns.index(targ_assign)] - - # Create the context - context = _ModJobContext(signer, fresh_targ_assign) - - # Modify it - await modifier(context) - - # Re-upload - await house_management.export_assignments(fresh_assigns) - - # Also import and update points - headers, points = await house_management.import_points() - house_management.apply_house_points(points, fresh_assigns) - house_management.export_points(headers, points) - - # If there aren't any jobs, say so - if len(closest_assigns) == 0: - if no_job_msg is None: - no_job_msg = "Unable to find any jobs to apply this command to. Try again with better spelling or whatever." - client.get_slack().reply(event, no_job_msg) - - # If theres only one job, sign it off - elif len(closest_assigns) == 1: - await success_callback(closest_assigns[0]) - - # If theres multiple jobs, we need to get a follow up! - else: - # Say we need more info - job_list = "\n".join("{}: {}".format(i, a.job.pretty_fmt()) for i, a in enumerate(closest_assigns)) - client.get_slack().reply(event, "Multiple relevant job listings found.\n" - "Please enter the number corresponding to the job " - "you wish to modify:\n{}".format(job_list)) - - # Establish a follow up command pattern - pattern = r"\d+" - - # Make the follow up callback - async def foc(_event: slack_util.Event, _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! - await success_callback(closest_assigns[index]) - else: - # They gave a bad index, or we were unable to find the assignment again. - client.get_slack().reply(_event, "Invalid job index / job unable to be found.") - - # Make a listener hook - new_hook = hooks.ReplyWaiter(foc, pattern, event.message.ts, 120) - - # Register it - client.get_slack().add_hook(new_hook) - - -async def signoff_callback(event: slack_util.Event, match: Match) -> None: - verb = slack_util.VerboseWrapper(event) - - print("Receving a signoff request!!!") - # Find out who we are trying to sign off is - signee_name = match.group(1) - signee = await verb(scroll_util.find_by_name(signee_name, MIN_RATIO)) - - # Score by name similarity, only accepting non-assigned jobs - def scorer(assign: house_management.JobAssignment): - if assign.assignee is not None: - r = fuzz.ratio(signee.name, assign.assignee.name) - if assign.signer is None and r > MIN_RATIO: - return r - - # Set the assigner, and notify - async def modifier(context: _ModJobContext): - context.assign.signer = context.signer - - # Say we did it wooo! - client.get_slack().reply(event, "Signed off {} for {}".format(context.assign.assignee.name, - context.assign.job.name)) - await alert_user(context.assign.assignee, "{} signed you off for {}.".format(context.assign.signer.name, - context.assign.job.pretty_fmt())) - - # Fire it off - await _mod_jobs(event, scorer, modifier) - - -async def undo_callback(event: slack_util.Event, match: Match) -> None: - verb = slack_util.VerboseWrapper(event) - - print("Receving an unsignoff request!!!") - # Find out who we are trying to sign off is - signee_name = match.group(1) - signee = await verb(scroll_util.find_by_name(signee_name, MIN_RATIO)) - - # Score by name similarity, only accepting jobs that are signed off - def scorer(assign: house_management.JobAssignment): - if assign.assignee is not None: - r = fuzz.ratio(signee.name, assign.assignee.name) - if assign.signer is not None and r > MIN_RATIO: - return r - - # Set the assigner to be None, and notify - async def modifier(context: _ModJobContext): - context.assign.signer = None - - # Say we did it wooo! - client.get_slack().reply(event, "Undid signoff of {} for {}".format(context.assign.assignee.name, - context.assign.job.name)) - await alert_user(context.assign.assignee, "{} undid your signoff off for {}.\n") - - # Fire it off - await _mod_jobs(event, scorer, modifier) - - -async def late_callback(event: slack_util.Event, match: Match) -> None: - verb = slack_util.VerboseWrapper(event) - - # Find out who we are trying to sign off is - signee_name = match.group(1) - signee = await verb(scroll_util.find_by_name(signee_name, MIN_RATIO)) - - # Score by name similarity. Don't care if signed off or not - def scorer(assign: house_management.JobAssignment): - if assign.assignee is not None: - r = fuzz.ratio(signee.name, assign.assignee.name) - if r > MIN_RATIO: - return r - - # Just set the assigner - async def modifier(context: _ModJobContext): - context.assign.late = not context.assign.late - - # Say we did it - client.get_slack().reply(event, "Toggled lateness of {}.\n" - "Now marked as late: {}".format(context.assign.job.pretty_fmt(), - context.assign.late)) - - # Fire it off - await _mod_jobs(event, scorer, modifier) - - -async def reassign_callback(event: slack_util.Event, match: Match) -> None: - verb = slack_util.VerboseWrapper(event) - - # Find out our two targets - from_name = match.group(1).strip() - to_name = match.group(2).strip() - - # Get them as brothers - from_bro = await verb(scroll_util.find_by_name(from_name, MIN_RATIO)) - to_bro = await verb(scroll_util.find_by_name(to_name, MIN_RATIO)) - - # Score by name similarity to the first brother. Don't care if signed off or not, - # as we want to be able to transfer even after signoffs (why not, amirite?) - def scorer(assign: house_management.JobAssignment): - if assign.assignee is not None: - r = fuzz.ratio(from_bro.name, assign.assignee.name) - if r > MIN_RATIO: - return r - - # Change the assignee - async def modifier(context: _ModJobContext): - context.assign.assignee = to_bro - - # Say we did it - reassign_msg = "Job {} reassigned from {} to {}".format(context.assign.job.pretty_fmt(), - from_bro, - to_bro) - client.get_slack().reply(event, reassign_msg) - - # Tell the people - reassign_msg = "Job {} reassigned from {} to {}".format(context.assign.job.pretty_fmt(), - from_bro, - to_bro) - await alert_user(from_bro, reassign_msg) - await alert_user(to_bro, reassign_msg) - - # Fire it off - await _mod_jobs(event, scorer, modifier) - - -# noinspection PyUnusedLocal -async def reset_callback(event: slack_util.Event, match: Match) -> None: - """ - Resets the scores. - """ - # Unassign everything - assigns = await house_management.import_assignments() - for a in assigns: - if a is not None: - a.signer = None - await house_management.export_assignments(assigns) - - # Now wipe points - headers, points = house_management.import_points() - - # Set to 0/default - for i in range(len(points)): - new = house_management.PointStatus(brother=points[i].brother) - points[i] = new - - house_management.apply_house_points(points, await house_management.import_assignments()) - house_management.export_points(headers, points) - - client.get_slack().reply(event, "Reset scores and signoffs") - - -# noinspection PyUnusedLocal -async def refresh_callback(event: slack_util.Event, match: Match) -> None: - print("Received request to reset points") - headers, points = await house_management.import_points() - print("Received request to reset points 1") - house_management.apply_house_points(points, await house_management.import_assignments()) - print("Received request to reset points 2") - house_management.export_points(headers, points) - print("Received request to reset points 3") - client.get_slack().reply(event, "Force updated point values") - - -async def nag_callback(event: slack_util.Event, match: Match) -> None: - # Get the day - day = match.group(1).lower().strip() - if not await nag_jobs(day): - client.get_slack().reply(event, - "No jobs found. Check that the day is spelled correctly, with no extra symbols.\n" - "It is possible that all jobs have been signed off, as well.", - in_thread=True) - - -# Wrapper so we can auto-call this as well -async def nag_jobs(day_of_week: str) -> bool: - # Get the assigns - assigns = await house_management.import_assignments() - - # Filter to day - assigns = [assign for assign in assigns if assign is not None and assign.job.day_of_week.lower() == day_of_week] - - # Filter signed off - assigns = [assign for assign in assigns if assign.signer is None] - - # If no jobs found, somethings up. Probably mispelled day. Return failure - if not assigns: - return False - - # Nag each - response = "Do yer jerbs! They are as follows:\n" - for assign in assigns: - # Make the row template - if assign.assignee is None: - continue - response += "({}) {} -- {} ".format(assign.job.house, assign.job.name, assign.assignee.name) - - # Find the people to @ - brother_slack_ids = await identifier.lookup_brother_userids(assign.assignee) - - if brother_slack_ids: - for slack_id in brother_slack_ids: - response += "<@{}> ".format(slack_id) - else: - response += "(scroll missing. Please register for @ pings!)" - response += "\n" - - general_id = client.get_slack().get_conversation_by_name("#general").id - client.get_slack().send_message(response, general_id) - return True - - -signoff_hook = hooks.ChannelHook(signoff_callback, - patterns=[ - r"signoff\s+(.*)", - r"sign off\s+(.*)", - ], - channel_whitelist=["#housejobs"]) - -undo_hook = hooks.ChannelHook(undo_callback, - patterns=[ - r"unsignoff\s+(.*)", - r"undosignoff\s+(.*)", - r"undo signoff\s+(.*)", - ], - channel_whitelist=["#housejobs"]) - -late_hook = hooks.ChannelHook(late_callback, - patterns=[ - r"marklate\s+(.*)", - r"mark late\s+(.*)", - ], - channel_whitelist=["#housejobs"]) - -reset_hook = hooks.ChannelHook(reset_callback, - patterns=[ - r"reset signoffs", - r"reset sign offs", - ], - channel_whitelist=["#command-center"]) - -nag_hook = hooks.ChannelHook(nag_callback, - patterns=[ - r"nagjobs\s+(.*)", - r"nag jobs\s+(.*)" - ], - channel_whitelist=["#command-center"]) - -reassign_hook = hooks.ChannelHook(reassign_callback, - patterns=r"reassign\s+(.*?)->\s+(.+)", - channel_whitelist=["#housejobs"]) - -refresh_hook = hooks.ChannelHook(refresh_callback, - patterns=[ - "refresh points", - "update points" - ], - channel_whitelist=["#command-center"]) - -block_action = """ -[ - { - "type": "actions", - "block_id": "test_block_id", - "elements": [ - { - "type": "button", - "action_id": "test_action_id", - "text": { - "type": "plain_text", - "text": "Send payload", - "emoji": false - } - } - ] - } -] -""" +import logging +from dataclasses import dataclass +from typing import List, Match, Callable, TypeVar, Optional, Iterable, Any, Coroutine + +from fuzzywuzzy import fuzz + +import hooks +from plugins import identifier, house_management, scroll_util +import client +import slack_util + +SHEET_ID = "1f9p4H7TWPm8rAM4v_qr2Vc6lBiFNEmR-quTY9UtxEBI" + +MIN_RATIO = 80.0 + + +async def alert_user(brother: scroll_util.Brother, saywhat: str) -> None: + """ + DM a brother saying something. Wrapper around several simpler methods + """ + # We do this as a for loop just in case multiple people reg. to same scroll for some reason (e.g. dup accounts) + succ = False + for slack_id in await identifier.lookup_brother_userids(brother): + client.get_slack().send_message(saywhat, slack_id) + succ = True + + # Warn if we never find + if not succ: + logging.warning("Unable to find dm conversation for brother {}".format(brother)) + + +# Generic type +T = TypeVar("T") + + +def tiemax(items: Iterable[T], key: Callable[[T], Optional[float]]) -> List[T]: + best = [] + best_score = None + for elt in items: + # Compute the score + score = key(elt) + + # Ignore blank scores + if score is None: + continue + # Check if its the new best, wiping old if so + elif best_score is None or score > best_score: + best_score = score + best = [elt] + # Check if its same as last best + elif score == best_score: + best.append(elt) + return best + + +@dataclass +class _ModJobContext: + signer: scroll_util.Brother # The brother invoking the command + assign: house_management.JobAssignment # The job assignment to modify + + +async def _mod_jobs(event: slack_util.Event, + relevance_scorer: Callable[[house_management.JobAssignment], Optional[float]], + modifier: Callable[[_ModJobContext], Coroutine[Any, Any, None]], + no_job_msg: str = None + ) -> None: + """ + Stub function that handles various tasks relating to modifying jobs + :param relevance_scorer: Function scores job assignments on relevance. Determines which gets modified + :param modifier: Callback function to modify a job. Only called on a successful operation, and only on one job + """ + # Make an error wrapper + verb = slack_util.VerboseWrapper(event) + + # Who invoked this command? + signer = await verb(event.user.as_user().get_brother()) + + # Get all of the assignments + assigns = await verb(house_management.import_assignments()) + + # Find closest assignment to what we're after. This just wraps relevance_scorer to handle nones. + def none_scorer(a: Optional[house_management.JobAssignment]) -> Optional[float]: + if a is None: + return None + else: + return relevance_scorer(a) + + closest_assigns = tiemax(assigns, key=none_scorer) + + # This is what we do on success. It will or won't be called immediately based on what's in closest_assigns + async def success_callback(targ_assign: house_management.JobAssignment) -> None: + # First get the most up to date version of the jobs + fresh_assigns = await verb(house_management.import_assignments()) + + # Find the one that matches what we had before + fresh_targ_assign = fresh_assigns[fresh_assigns.index(targ_assign)] + + # Create the context + context = _ModJobContext(signer, fresh_targ_assign) + + # Modify it + await modifier(context) + + # Re-upload + await house_management.export_assignments(fresh_assigns) + + # Also import and update points + headers, points = await house_management.import_points() + house_management.apply_house_points(points, fresh_assigns) + house_management.export_points(headers, points) + + # If there aren't any jobs, say so + if len(closest_assigns) == 0: + if no_job_msg is None: + no_job_msg = "Unable to find any jobs to apply this command to. Try again with better spelling or whatever." + client.get_slack().reply(event, no_job_msg) + + # If theres only one job, sign it off + elif len(closest_assigns) == 1: + await success_callback(closest_assigns[0]) + + # If theres multiple jobs, we need to get a follow up! + else: + # Say we need more info + job_list = "\n".join("{}: {}".format(i, a.job.pretty_fmt()) for i, a in enumerate(closest_assigns)) + client.get_slack().reply(event, "Multiple relevant job listings found.\n" + "Please enter the number corresponding to the job " + "you wish to modify:\n{}".format(job_list)) + + # Establish a follow up command pattern + pattern = r"\d+" + + # Make the follow up callback + async def foc(_event: slack_util.Event, _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! + await success_callback(closest_assigns[index]) + else: + # They gave a bad index, or we were unable to find the assignment again. + client.get_slack().reply(_event, "Invalid job index / job unable to be found.") + + # Make a listener hook + new_hook = hooks.ReplyWaiter(foc, pattern, event.message.ts, 120) + + # Register it + client.get_slack().add_hook(new_hook) + + +async def signoff_callback(event: slack_util.Event, match: Match) -> None: + verb = slack_util.VerboseWrapper(event) + + print("Receving a signoff request!!!") + # Find out who we are trying to sign off is + signee_name = match.group(1) + signee = await verb(scroll_util.find_by_name(signee_name, MIN_RATIO)) + + # Score by name similarity, only accepting non-assigned jobs + def scorer(assign: house_management.JobAssignment): + if assign.assignee is not None: + r = fuzz.ratio(signee.name, assign.assignee.name) + if assign.signer is None and r > MIN_RATIO: + return r + + # Set the assigner, and notify + async def modifier(context: _ModJobContext): + context.assign.signer = context.signer + + # Say we did it wooo! + client.get_slack().reply(event, "Signed off {} for {}".format(context.assign.assignee.name, + context.assign.job.name)) + await alert_user(context.assign.assignee, "{} signed you off for {}.".format(context.assign.signer.name, + context.assign.job.pretty_fmt())) + + # Fire it off + await _mod_jobs(event, scorer, modifier) + + +async def undo_callback(event: slack_util.Event, match: Match) -> None: + verb = slack_util.VerboseWrapper(event) + + print("Receving an unsignoff request!!!") + # Find out who we are trying to sign off is + signee_name = match.group(1) + signee = await verb(scroll_util.find_by_name(signee_name, MIN_RATIO)) + + # Score by name similarity, only accepting jobs that are signed off + def scorer(assign: house_management.JobAssignment): + if assign.assignee is not None: + r = fuzz.ratio(signee.name, assign.assignee.name) + if assign.signer is not None and r > MIN_RATIO: + return r + + # Set the assigner to be None, and notify + async def modifier(context: _ModJobContext): + context.assign.signer = None + + # Say we did it wooo! + client.get_slack().reply(event, "Undid signoff of {} for {}".format(context.assign.assignee.name, + context.assign.job.name)) + await alert_user(context.assign.assignee, "{} undid your signoff off for {}.\n") + + # Fire it off + await _mod_jobs(event, scorer, modifier) + + +async def late_callback(event: slack_util.Event, match: Match) -> None: + verb = slack_util.VerboseWrapper(event) + + # Find out who we are trying to sign off is + signee_name = match.group(1) + signee = await verb(scroll_util.find_by_name(signee_name, MIN_RATIO)) + + # Score by name similarity. Don't care if signed off or not + def scorer(assign: house_management.JobAssignment): + if assign.assignee is not None: + r = fuzz.ratio(signee.name, assign.assignee.name) + if r > MIN_RATIO: + return r + + # Just set the assigner + async def modifier(context: _ModJobContext): + context.assign.late = not context.assign.late + + # Say we did it + client.get_slack().reply(event, "Toggled lateness of {}.\n" + "Now marked as late: {}".format(context.assign.job.pretty_fmt(), + context.assign.late)) + + # Fire it off + await _mod_jobs(event, scorer, modifier) + + +async def reassign_callback(event: slack_util.Event, match: Match) -> None: + verb = slack_util.VerboseWrapper(event) + + # Find out our two targets + from_name = match.group(1).strip() + to_name = match.group(2).strip() + + # Get them as brothers + from_bro = await verb(scroll_util.find_by_name(from_name, MIN_RATIO)) + to_bro = await verb(scroll_util.find_by_name(to_name, MIN_RATIO)) + + # Score by name similarity to the first brother. Don't care if signed off or not, + # as we want to be able to transfer even after signoffs (why not, amirite?) + def scorer(assign: house_management.JobAssignment): + if assign.assignee is not None: + r = fuzz.ratio(from_bro.name, assign.assignee.name) + if r > MIN_RATIO: + return r + + # Change the assignee + async def modifier(context: _ModJobContext): + context.assign.assignee = to_bro + + # Say we did it + reassign_msg = "Job {} reassigned from {} to {}".format(context.assign.job.pretty_fmt(), + from_bro, + to_bro) + client.get_slack().reply(event, reassign_msg) + + # Tell the people + reassign_msg = "Job {} reassigned from {} to {}".format(context.assign.job.pretty_fmt(), + from_bro, + to_bro) + await alert_user(from_bro, reassign_msg) + await alert_user(to_bro, reassign_msg) + + # Fire it off + await _mod_jobs(event, scorer, modifier) + + +# noinspection PyUnusedLocal +async def reset_callback(event: slack_util.Event, match: Match) -> None: + """ + Resets the scores. + """ + # Unassign everything + assigns = await house_management.import_assignments() + for a in assigns: + if a is not None: + a.signer = None + await house_management.export_assignments(assigns) + + # Now wipe points + headers, points = await house_management.import_points() + + # Set to 0/default + for i in range(len(points)): + new = house_management.PointStatus(brother=points[i].brother) + points[i] = new + + house_management.apply_house_points(points, await house_management.import_assignments()) + house_management.export_points(headers, points) + + client.get_slack().reply(event, "Reset scores and signoffs") + + +# noinspection PyUnusedLocal +async def refresh_callback(event: slack_util.Event, match: Match) -> None: + print("Received request to reset points") + headers, points = await house_management.import_points() + print("Received request to reset points 1") + house_management.apply_house_points(points, await house_management.import_assignments()) + print("Received request to reset points 2") + house_management.export_points(headers, points) + print("Received request to reset points 3") + client.get_slack().reply(event, "Force updated point values") + + +async def nag_callback(event: slack_util.Event, match: Match) -> None: + # Get the day + day = match.group(1).lower().strip() + if not await nag_jobs(day): + client.get_slack().reply(event, + "No jobs found. Check that the day is spelled correctly, with no extra symbols.\n" + "It is possible that all jobs have been signed off, as well.", + in_thread=True) + + +# Wrapper so we can auto-call this as well +async def nag_jobs(day_of_week: str) -> bool: + # Get the assigns + assigns = await house_management.import_assignments() + + # Filter to day + assigns = [assign for assign in assigns if assign is not None and assign.job.day_of_week.lower() == day_of_week] + + # Filter signed off + assigns = [assign for assign in assigns if assign.signer is None] + + # If no jobs found, somethings up. Probably mispelled day. Return failure + if not assigns: + return False + + # Nag each + response = "Do yer jerbs! They are as follows:\n" + for assign in assigns: + # Make the row template + if assign.assignee is None: + continue + response += "({}) {} -- {} ".format(assign.job.house, assign.job.name, assign.assignee.name) + + # Find the people to @ + brother_slack_ids = await identifier.lookup_brother_userids(assign.assignee) + + if brother_slack_ids: + for slack_id in brother_slack_ids: + response += "<@{}> ".format(slack_id) + else: + response += "(scroll missing. Please register for @ pings!)" + response += "\n" + + general_id = client.get_slack().get_conversation_by_name("#general").id + client.get_slack().send_message(response, general_id) + return True + + +signoff_hook = hooks.ChannelHook(signoff_callback, + patterns=[ + r"signoff\s+(.*)", + r"sign off\s+(.*)", + ], + channel_whitelist=["#housejobs"]) + +undo_hook = hooks.ChannelHook(undo_callback, + patterns=[ + r"unsignoff\s+(.*)", + r"undosignoff\s+(.*)", + r"undo signoff\s+(.*)", + ], + channel_whitelist=["#housejobs"]) + +late_hook = hooks.ChannelHook(late_callback, + patterns=[ + r"marklate\s+(.*)", + r"mark late\s+(.*)", + ], + channel_whitelist=["#housejobs"]) + +reset_hook = hooks.ChannelHook(reset_callback, + patterns=[ + r"reset signoffs", + r"reset sign offs", + ], + channel_whitelist=["#command-center"]) + +nag_hook = hooks.ChannelHook(nag_callback, + patterns=[ + r"nagjobs\s+(.*)", + r"nag jobs\s+(.*)" + ], + channel_whitelist=["#command-center"]) + +reassign_hook = hooks.ChannelHook(reassign_callback, + patterns=r"reassign\s+(.*?)->\s+(.+)", + channel_whitelist=["#housejobs"]) + +refresh_hook = hooks.ChannelHook(refresh_callback, + patterns=[ + "refresh points", + "update points" + ], + channel_whitelist=["#command-center"]) + +block_action = """ +[ + { + "type": "actions", + "block_id": "test_block_id", + "elements": [ + { + "type": "button", + "action_id": "test_action_id", + "text": { + "type": "plain_text", + "text": "Send payload", + "emoji": false + } + } + ] + } +] +"""