Moved to async

This commit is contained in:
Jacob Henry 2018-11-08 00:27:06 -05:00
parent df3e93c35f
commit 33557790bb
9 changed files with 52 additions and 34 deletions

View File

@ -13,7 +13,7 @@ HOUSEJOBS = "CDWDDTAT0"
# Callback for telling what channel we in # Callback for telling what channel we in
def channel_check_callback(slack: SlackClient, msg: dict, match: Match) -> None: async def channel_check_callback(slack: SlackClient, msg: dict, match: Match) -> None:
# Sets the users scroll # Sets the users scroll
rest_of_msg = match.group(1).strip() rest_of_msg = match.group(1).strip()
rest_of_msg = rest_of_msg.replace("<", "lcaret") rest_of_msg = rest_of_msg.replace("<", "lcaret")

View File

@ -19,7 +19,7 @@ NON_REG_MSG = ("You currently have no scroll registered. To register, type\n"
"except with your scroll instead of 666") "except with your scroll instead of 666")
def identify_callback(slack, msg, match): async def identify_callback(slack, msg, match):
""" """
Sets the users scroll Sets the users scroll
""" """
@ -39,7 +39,7 @@ def identify_callback(slack, msg, match):
slack_util.reply(slack, msg, result) slack_util.reply(slack, msg, result)
def identify_other_callback(slack: SlackClient, msg: dict, match: Match): async def identify_other_callback(slack: SlackClient, msg: dict, match: Match):
""" """
Sets another users scroll Sets another users scroll
""" """
@ -63,7 +63,7 @@ def identify_other_callback(slack: SlackClient, msg: dict, match: Match):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def check_callback(slack: SlackClient, msg: dict, match: Match): async def check_callback(slack: SlackClient, msg: dict, match: Match):
""" """
Replies with the users current scroll assignment Replies with the users current scroll assignment
""" """
@ -78,7 +78,7 @@ def check_callback(slack: SlackClient, msg: dict, match: Match):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def name_callback(slack, msg, match): async def name_callback(slack, msg, match):
""" """
Tells the user what it thinks the calling users name is. Tells the user what it thinks the calling users name is.
""" """
@ -119,7 +119,7 @@ def lookup_slackid_brother(slack_id: str) -> Optional[scroll_util.Brother]:
return None return None
def lookup_brother_userids(brother: scroll_util.Brother) -> List[str]: async def lookup_brother_userids(brother: scroll_util.Brother) -> List[str]:
""" """
Returns a list of all userids associated with the given brother. Returns a list of all userids associated with the given brother.

View File

@ -52,7 +52,7 @@ def get_jobs(day=None):
return jobs return jobs
def nag_callback(slack, msg, match): async def nag_callback(slack, msg, match):
# Get the day # Get the day
day = match.group(1).lower().strip() day = match.group(1).lower().strip()
jobs = get_jobs(day) jobs = get_jobs(day)

View File

@ -78,7 +78,7 @@ def alert_user(slack: SlackClient, name: str, saywhat: str) -> None:
print("Warning: unable to find dm for brother {}".format(brother_dict)) print("Warning: unable to find dm for brother {}".format(brother_dict))
def signoff_callback(slack: SlackClient, msg: dict, match: Match) -> None: async def signoff_callback(slack: SlackClient, msg: dict, match: Match) -> None:
""" """
Callback to signoff a user. Callback to signoff a user.
""" """
@ -101,7 +101,7 @@ def signoff_callback(slack: SlackClient, msg: dict, match: Match) -> None:
slack_util.reply(slack, msg, e.as_response()) slack_util.reply(slack, msg, e.as_response())
def punish_callback(slack: SlackClient, msg: dict, match: Match) -> None: async def punish_callback(slack: SlackClient, msg: dict, match: Match) -> None:
""" """
Undoes a signoff. Maybe should rename Undoes a signoff. Maybe should rename
""" """
@ -132,7 +132,7 @@ def punish_callback(slack: SlackClient, msg: dict, match: Match) -> None:
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def reset_callback(slack: SlackClient, msg: dict, match: Match) -> None: async def reset_callback(slack: SlackClient, msg: dict, match: Match) -> None:
""" """
Resets the scores. Resets the scores.
""" """

40
main.py
View File

@ -1,3 +1,4 @@
import typing
from typing import List from typing import List
from slackclient import SlackClient # Obvious from slackclient import SlackClient # Obvious
@ -10,6 +11,7 @@ import scroll_util
import slack_util import slack_util
import slavestothemachine import slavestothemachine
import job_signoff import job_signoff
import asyncio
from dummy import FakeClient from dummy import FakeClient
# Read api token from file # Read api token from file
@ -20,13 +22,9 @@ api_file.close()
# Enable to use dummy # Enable to use dummy
DEBUG_MODE = False DEBUG_MODE = False
def main() -> None: def main() -> None:
wrap = ClientWrapper() wrap = ClientWrapper()
# DEBUG: Add blanked handling
# wrapper.add_hook(".*", print)
# Add scroll handling # Add scroll handling
wrap.add_hook(scroll_util.scroll_hook) wrap.add_hook(scroll_util.scroll_hook)
@ -58,12 +56,16 @@ def main() -> None:
help_callback = management_commands.list_hooks_callback_gen(wrap.hooks) help_callback = management_commands.list_hooks_callback_gen(wrap.hooks)
wrap.add_hook(slack_util.Hook(help_callback, pattern=management_commands.bot_help_pattern)) wrap.add_hook(slack_util.Hook(help_callback, pattern=management_commands.bot_help_pattern))
wrap.listen() event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(wrap.listen())
# Callback to list command hooks
class ClientWrapper(object): class ClientWrapper(object):
"""
Essentially the main state object.
We only ever expect one of these.
Holds a slack client, and handles messsages.
"""
def __init__(self): def __init__(self):
# Init slack # Init slack
if DEBUG_MODE: if DEBUG_MODE:
@ -80,9 +82,9 @@ class ClientWrapper(object):
def add_hook(self, hook: slack_util.Hook) -> None: def add_hook(self, hook: slack_util.Hook) -> None:
self.hooks.append(hook) self.hooks.append(hook)
def listen(self) -> None: async def listen(self) -> None:
feed = slack_util.message_stream(self.slack) feed = self.async_message_feed()
for msg in feed: async for msg in feed:
print(msg) print(msg)
# We only care about standard messages, not subtypes, as those usually just channel activity # We only care about standard messages, not subtypes, as those usually just channel activity
@ -106,13 +108,29 @@ class ClientWrapper(object):
success = False success = False
for hook in self.hooks: for hook in self.hooks:
if hook.check(slack_to_use, msg): if await hook.check(slack_to_use, msg):
success = True success = True
break break
if not success: if not success:
print("No hit on {}".format(msg['text'])) print("No hit on {}".format(msg['text']))
async def async_message_feed(self) -> typing.AsyncGenerator[dict, None]:
"""
Async wrapper around the message feed.
Yields messages awaitably forever.
"""
# Create the msg feed
feed = slack_util.message_stream(self.slack)
# Create a simple callable that gets one message from the feed
def get_one():
return next(feed)
# Continuously yield async threaded tasks that poll the feed
while True:
yield await asyncio.get_running_loop().run_in_executor(None, get_one)
# run main # run main
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -6,9 +6,9 @@ import channel_util
import slack_util import slack_util
def list_hooks_callback_gen(hooks: List[slack_util.Hook]): async def list_hooks_callback_gen(hooks: List[slack_util.Hook]):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def callback(slack, msg, match): async def callback(slack, msg, match):
slack_util.reply(slack, msg, "\n".join(hook.pattern for hook in hooks)) slack_util.reply(slack, msg, "\n".join(hook.pattern for hook in hooks))
return callback return callback
@ -16,7 +16,7 @@ def list_hooks_callback_gen(hooks: List[slack_util.Hook]):
# Gracefully reboot to reload code changes # Gracefully reboot to reload code changes
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def reboot_callback(slack: SlackClient, msg: dict, match: Match) -> None: async def reboot_callback(slack: SlackClient, msg: dict, match: Match) -> None:
response = "Ok. Rebooting..." response = "Ok. Rebooting..."
slack_util.reply(slack, msg, response) slack_util.reply(slack, msg, response)
exit(0) exit(0)

View File

@ -31,7 +31,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]
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.
""" """

View File

@ -4,7 +4,7 @@ import re
from slackclient import SlackClient from slackclient import SlackClient
import channel_util import channel_util
from typing import Any, Optional, Generator, Match, Callable, List from typing import Any, Optional, Generator, Match, Callable, List, Awaitable
""" """
Slack helpers. Separated for compartmentalization Slack helpers. Separated for compartmentalization
@ -61,7 +61,7 @@ class SlackDebugCondom(object):
return self.actual_slack.__getattribute__(name) return self.actual_slack.__getattribute__(name)
def message_stream(slack) -> Generator[dict, None, None]: def message_stream(slack: SlackClient) -> Generator[dict, 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.
@ -72,19 +72,19 @@ def message_stream(slack) -> Generator[dict, None, None]:
if slack.rtm_connect(with_team_state=False, auto_reconnect=True): if slack.rtm_connect(with_team_state=False, auto_reconnect=True):
print("Waiting for messages") print("Waiting for messages")
while True: while True:
sleep(2) sleep(1)
update = slack.rtm_read() update = slack.rtm_read()
for item in update: for item in update:
if item.get('type') == 'message': if item.get('type') == 'message':
yield item yield item
sleep(15) sleep(5)
print("Connection failed - retrying") print("Connection failed - retrying")
class Hook(object): class Hook(object):
def __init__(self, def __init__(self,
callback: Callable[[SlackClient, dict, Match], None], callback: Callable[[SlackClient, dict, Match], Awaitable[None]], # TODO: Fix this type
pattern: str = None, pattern: str = None,
channel_whitelist: Optional[List[str]] = None, channel_whitelist: Optional[List[str]] = None,
channel_blacklist: Optional[List[str]] = None): channel_blacklist: Optional[List[str]] = None):
@ -106,7 +106,7 @@ class Hook(object):
else: else:
raise Exception("Cannot whitelist and blacklist") raise Exception("Cannot whitelist and blacklist")
def check(self, slack: SlackClient, msg: dict) -> bool: async def check(self, slack: SlackClient, msg: dict) -> bool:
# Fail if pattern invalid # Fail if pattern invalid
match = re.match(self.pattern, msg['text'], flags=re.IGNORECASE) match = re.match(self.pattern, msg['text'], flags=re.IGNORECASE)
if match is None: if match is None:
@ -125,5 +125,5 @@ class Hook(object):
# Otherwise do callback and return success # Otherwise do callback and return success
print("Matched on pattern {} callback {}".format(self.pattern, self.callback)) print("Matched on pattern {} callback {}".format(self.pattern, self.callback))
self.callback(slack, msg, match) await self.callback(slack, msg, match)
return True return True

View File

@ -19,7 +19,7 @@ def fmt_work_dict(work_dict: dict) -> str:
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def count_work_callback(slack: SlackClient, msg: dict, match: Match) -> None: async def count_work_callback(slack: SlackClient, msg: dict, match: Match) -> None:
with shelve.open(DB_NAME) as db: with shelve.open(DB_NAME) as db:
text = msg["text"].lower().strip() text = msg["text"].lower().strip()
@ -64,7 +64,7 @@ def count_work_callback(slack: SlackClient, msg: dict, match: Match) -> None:
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def dump_work_callback(slack: SlackClient, msg: dict, match: Match) -> None: async def dump_work_callback(slack: SlackClient, msg: dict, match: Match) -> None:
with shelve.open(DB_NAME) as db: with shelve.open(DB_NAME) as db:
# Dump out each user # Dump out each user
keys = db.keys() keys = db.keys()