diff --git a/job_commands.py b/job_commands.py index 5dd83f1..411fffe 100644 --- a/job_commands.py +++ b/job_commands.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import List, Match, Callable, TypeVar, Optional, Iterable +from typing import List, Match, Callable, TypeVar, Optional, Iterable, Any, Coroutine from fuzzywuzzy import fuzz @@ -60,7 +60,7 @@ class _ModJobContext: async def _mod_jobs(event: slack_util.Event, relevance_scorer: Callable[[house_management.JobAssignment], Optional[float]], - modifier: Callable[[_ModJobContext], None], + modifier: Callable[[_ModJobContext], Coroutine[Any, Any, None]], no_job_msg: str = None ) -> None: """ @@ -98,7 +98,7 @@ async def _mod_jobs(event: slack_util.Event, context = _ModJobContext(signer, fresh_targ_assign) # Modify it - modifier(context) + await modifier(context) # Re-upload await house_management.export_assignments(fresh_assigns) @@ -164,14 +164,14 @@ async def signoff_callback(event: slack_util.Event, match: Match) -> None: return r # Set the assigner, and notify - def modifier(context: _ModJobContext): + async def modifier(context: _ModJobContext): context.assign.signer = context.signer # Say we did it wooo! slack_util.get_slack().reply(event, "Signed off {} for {}".format(context.assign.assignee.name, context.assign.job.name)) - alert_user(context.assign.assignee, "{} signed you off for {}.".format(context.assign.signer.name, - context.assign.job.pretty_fmt())) + 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) @@ -192,15 +192,15 @@ async def undo_callback(event: slack_util.Event, match: Match) -> None: return r # Set the assigner to be None, and notify - def modifier(context: _ModJobContext): + async def modifier(context: _ModJobContext): context.assign.signer = None # Say we did it wooo! slack_util.get_slack().reply(event, "Undid signoff of {} for {}".format(context.assign.assignee.name, context.assign.job.name)) - alert_user(context.assign.assignee, "{} undid your signoff off for {}.\n" - "Must have been a mistake".format(context.assign.signer.name, - context.assign.job.pretty_fmt())) + await alert_user(context.assign.assignee, "{} undid your signoff off for {}.\n" + "Must have been a mistake".format(context.assign.signer.name, + context.assign.job.pretty_fmt())) # Fire it off await _mod_jobs(event, scorer, modifier) @@ -221,7 +221,7 @@ async def late_callback(event: slack_util.Event, match: Match) -> None: return r # Just set the assigner - def modifier(context: _ModJobContext): + async def modifier(context: _ModJobContext): context.assign.late = not context.assign.late # Say we did it @@ -253,7 +253,7 @@ async def reassign_callback(event: slack_util.Event, match: Match) -> None: return r # Change the assignee - def modifier(context: _ModJobContext): + async def modifier(context: _ModJobContext): context.assign.assignee = to_bro # Say we did it @@ -266,8 +266,8 @@ async def reassign_callback(event: slack_util.Event, match: Match) -> None: reassign_msg = "Job {} reassigned from {} to {}".format(context.assign.job.pretty_fmt(), from_bro, to_bro) - alert_user(from_bro, reassign_msg) - alert_user(to_bro, reassign_msg) + await alert_user(from_bro, reassign_msg) + await alert_user(to_bro, reassign_msg) # Fire it off await _mod_jobs(event, scorer, modifier) @@ -360,7 +360,7 @@ signoff_hook = slack_util.ChannelHook(signoff_callback, r"signoff\s+(.*)", r"sign off\s+(.*)", ], - channel_whitelist=["#housejobs"]) + channel_whitelist=["#botzone"]) undo_hook = slack_util.ChannelHook(undo_callback, patterns=[ diff --git a/slack_util.py b/slack_util.py index 9366292..da5f4c2 100644 --- a/slack_util.py +++ b/slack_util.py @@ -1,5 +1,5 @@ from __future__ import annotations - +from aiohttp import web import asyncio import re import sys @@ -129,25 +129,7 @@ def message_stream(slack: SlackClient) -> Generator[Event, None, None]: # Handle each for update in update_list: print("Message received: {}".format(update)) - event = Event() - - # Big logic folks - if update["type"] == "message": - # For now we only handle these basic types of messages involving text - # TODO: Handle "unwrappeable" messages - if "text" in update and "ts" in update: - event.message = MessageContext(update["ts"], update["text"]) - if "channel" in update: - event.conversation = ConversationContext(update["channel"]) - if "user" in update: - event.user = UserContext(update["user"]) - if "thread_ts" in update: - event.thread = ThreadContext(update["thread_ts"]) - - # TODO: Handle more types - # We need to - - yield event + yield dict_to_event(update) except (SlackNotConnected, OSError) as e: print("Error while reading messages:") @@ -160,6 +142,30 @@ def message_stream(slack: SlackClient) -> Generator[Event, None, None]: print("Connection failed - retrying") +def dict_to_event(update: dict) -> Event: + """ + Converts a dict update to an actual event. + """ + event = Event() + + # Big logic folks + if update["type"] == "message": + # For now we only handle these basic types of messages involving text + # TODO: Handle "unwrappeable" messages + if "text" in update and "ts" in update: + event.message = MessageContext(update["ts"], update["text"]) + if "channel" in update: + event.conversation = ConversationContext(update["channel"]) + if "user" in update: + event.user = UserContext(update["user"]) + if "thread_ts" in update: + event.thread = ThreadContext(update["thread_ts"]) + + # TODO: Handle more types of events, including http data etc. + + return event + + """ Objects to wrap slack connections """ @@ -254,8 +260,19 @@ class ClientWrapper(object): yield await asyncio.get_running_loop().run_in_executor(None, get_one) async def http_event_feed(self) -> AsyncGenerator[Event, None]: + # Create a callback to convert requests to events + async def interr(request: web.Request): + return web.Response() + # Create the server - pass + app = web.Application() + app.add_routes([web.get('/bothttpcallback/', interr)]) + + # Asynchronously serve that boy up + runner = web.AppRunner(app) + await runner.setup() + site = web.TCPSite(runner, 'localhost', 8080) + await site.start() while True: await asyncio.sleep(30) @@ -293,14 +310,14 @@ class ClientWrapper(object): def get_conversation_by_name(self, conversation_identifier: str) -> Optional[Conversation]: # If looking for a direct message, first lookup user, then fetch if conversation_identifier[0] == "@": - user_name = conversation_identifier[1:] + user_name = conversation_identifier # Find the user by their name raise NotImplementedError("There wasn't a clear use case for this yet, so we've opted to just not use it") # If looking for a channel, just lookup normally elif conversation_identifier[0] == "#": - channel_name = conversation_identifier[1:] + channel_name = conversation_identifier # Find the channel in the dict for channel in self.conversations.values(): @@ -383,10 +400,10 @@ class ClientWrapper(object): for channel_dict in channel_dicts["channels"]: if channel_dict["is_im"]: new_channel = DirectMessage(id=channel_dict["id"], - user_id=channel_dict["user"]) + user_id="@" + channel_dict["user"]) else: new_channel = Channel(id=channel_dict["id"], - name=channel_dict["name"]) + name="#" + channel_dict["name"]) new_dict[new_channel.id] = new_channel # Fetch the cursor @@ -513,8 +530,6 @@ class ChannelHook(AbsHook): if not (event.conversation and event.message and isinstance(event.conversation.get_conversation(), Channel)): return None - - # Fail if pattern invalid match = None for p in self.patterns: