Awaiting testing

This commit is contained in:
Jacob Henry 2019-02-22 05:27:03 -05:00
parent ddb6ffac10
commit c1dbe21c77
5 changed files with 170 additions and 51 deletions

View File

@ -2,7 +2,6 @@ from dataclasses import dataclass
from typing import List, Match, Callable, TypeVar, Optional, Iterable from typing import List, Match, Callable, TypeVar, Optional, Iterable
from fuzzywuzzy import fuzz from fuzzywuzzy import fuzz
from slackclient import SlackClient
import house_management import house_management
import identifier import identifier
@ -124,8 +123,8 @@ async def _mod_jobs(event: slack_util.Event,
# Say we need more info # Say we need more info
job_list = "\n".join("{}: {}".format(i, a.job.pretty_fmt()) for i, a in enumerate(closest_assigns)) job_list = "\n".join("{}: {}".format(i, a.job.pretty_fmt()) for i, a in enumerate(closest_assigns))
slack_util.get_slack().reply(event, "Multiple relevant job listings found.\n" slack_util.get_slack().reply(event, "Multiple relevant job listings found.\n"
"Please enter the number corresponding to the job " "Please enter the number corresponding to the job "
"you wish to modify:\n{}".format(job_list)) "you wish to modify:\n{}".format(job_list))
# Establish a follow up command pattern # Establish a follow up command pattern
pattern = r"\d+" pattern = r"\d+"
@ -170,7 +169,7 @@ async def signoff_callback(event: slack_util.Event, match: Match) -> None:
# Say we did it wooo! # Say we did it wooo!
slack_util.get_slack().reply(event, "Signed off {} for {}".format(context.assign.assignee.name, slack_util.get_slack().reply(event, "Signed off {} for {}".format(context.assign.assignee.name,
context.assign.job.name)) context.assign.job.name))
alert_user(context.assign.assignee, "{} signed you off for {}.".format(context.assign.signer.name, alert_user(context.assign.assignee, "{} signed you off for {}.".format(context.assign.signer.name,
context.assign.job.pretty_fmt())) context.assign.job.pretty_fmt()))
@ -198,7 +197,7 @@ async def undo_callback(event: slack_util.Event, match: Match) -> None:
# Say we did it wooo! # Say we did it wooo!
slack_util.get_slack().reply(event, "Undid signoff of {} for {}".format(context.assign.assignee.name, slack_util.get_slack().reply(event, "Undid signoff of {} for {}".format(context.assign.assignee.name,
context.assign.job.name)) context.assign.job.name))
alert_user(context.assign.assignee, "{} undid your signoff off for {}.\n" alert_user(context.assign.assignee, "{} undid your signoff off for {}.\n"
"Must have been a mistake".format(context.assign.signer.name, "Must have been a mistake".format(context.assign.signer.name,
context.assign.job.pretty_fmt())) context.assign.job.pretty_fmt()))
@ -227,8 +226,8 @@ async def late_callback(event: slack_util.Event, match: Match) -> None:
# Say we did it # Say we did it
slack_util.get_slack().reply(event, "Toggled lateness of {}.\n" slack_util.get_slack().reply(event, "Toggled lateness of {}.\n"
"Now marked as late: {}".format(context.assign.job.pretty_fmt(), "Now marked as late: {}".format(context.assign.job.pretty_fmt(),
context.assign.late)) context.assign.late))
# Fire it off # Fire it off
await _mod_jobs(event, scorer, modifier) await _mod_jobs(event, scorer, modifier)
@ -311,22 +310,27 @@ async def refresh_callback(event: slack_util.Event, match: Match) -> None:
async def nag_callback(event: slack_util.Event, match: Match) -> None: async def nag_callback(event: slack_util.Event, match: Match) -> None:
# Get the day # Get the day
day = match.group(1).lower().strip() day = match.group(1).lower().strip()
if not await nag_jobs(day):
slack_util.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 # Get the assigns
assigns = await house_management.import_assignments() assigns = await house_management.import_assignments()
# Filter to day # Filter to day
assigns = [assign for assign in assigns if assign is not None and assign.job.day_of_week.lower() == 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 # Filter signed off
assigns = [assign for assign in assigns if assign.signer is None] assigns = [assign for assign in assigns if assign.signer is None]
# If no jobs found, somethings up. Probably mispelled day. # If no jobs found, somethings up. Probably mispelled day. Return failure
if not assigns: if not assigns:
slack_util.get_slack().reply(event, "No jobs found. Check that the day is spelled correctly, with no extra symbols.\n" return False
"It is possible that all jobs have been signed off, as well.",
in_thread=True)
return
# Nag each # Nag each
response = "Do yer jerbs! They are as follows:\n" response = "Do yer jerbs! They are as follows:\n"
@ -347,7 +351,7 @@ async def nag_callback(event: slack_util.Event, match: Match) -> None:
response += "\n" response += "\n"
general_id = slack_util.get_slack().get_channel_by_name("#general").id general_id = slack_util.get_slack().get_channel_by_name("#general").id
slack_util.get_slack().reply(event, response, in_thread=False, to_channel=general_id) slack_util.get_slack().send_message(response, general_id)
signoff_hook = slack_util.ChannelHook(signoff_callback, signoff_hook = slack_util.ChannelHook(signoff_callback,

View File

@ -44,6 +44,9 @@ def main() -> None:
# Add boozebot # Add boozebot
# wrap.add_passive(periodicals.ItsTenPM()) # wrap.add_passive(periodicals.ItsTenPM())
# Add automatic updating of users
wrap.add_passive(periodicals.Updatinator(wrap, 60))
# Add nagloop # Add nagloop
wrap.add_passive(periodicals.RemindJobs()) wrap.add_passive(periodicals.RemindJobs())

View File

@ -4,6 +4,7 @@ from typing import Optional, List
import house_management import house_management
import identifier import identifier
import job_commands
import slack_util import slack_util
@ -30,21 +31,64 @@ class ItsTenPM(slack_util.Passive):
await asyncio.sleep(60) await asyncio.sleep(60)
class RemindJobs(slack_util.Passive): # Shared behaviour
class JobNotifier:
@staticmethod
def get_day_of_week() -> str:
"""
Gets the current day of week as a str
"""
return ["Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"][datetime.now().weekday()]
@staticmethod
def is_job_valid(a: Optional[house_management.JobAssignment]):
# If it doesn't exist, it a thot
if a is None:
return False
# If its not today, we shouldn't nag
if a.job.day_of_week.lower() != JobNotifier.get_day_of_week().lower():
return False
# If it is unassigned, we can't nag
if a.assignee is None:
return False
# If its been signed off, no need to nag
if a.signer is not None:
return False
# If the brother wasn't recognized, don't try nagging
if not a.assignee.is_valid():
return False
return True
class NotifyJobs(slack_util.Passive, JobNotifier):
async def run(self) -> None:
while True:
# Get the "Start" of the current day (Say, 10AM)
today_remind_time = datetime.now().replace(hour=10, minute=00, second=0)
# Sleep until that time
delay = seconds_until(today_remind_time)
await asyncio.sleep(delay)
# Now it is that time. Nag the jobs
job_commands.nag_jobs(self.get_day_of_week())
# Sleep for a bit to prevent double shots
await asyncio.sleep(10)
class RemindJobs(slack_util.Passive, JobNotifier):
async def run(self) -> None: async def run(self) -> None:
while True: while True:
# Get the end of the current day (Say, 10PM) # Get the end of the current day (Say, 10PM)
today_remind_time = datetime.now().replace(hour=22, minute=00, second=0) today_remind_time = datetime.now().replace(hour=22, minute=00, second=0)
# Get the current day of week
dow = ["Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"][datetime.now().weekday()]
# Sleep until that time # Sleep until that time
delay = seconds_until(today_remind_time) delay = seconds_until(today_remind_time)
await asyncio.sleep(delay) await asyncio.sleep(delay)
@ -53,28 +97,10 @@ class RemindJobs(slack_util.Passive):
assigns = await house_management.import_assignments() assigns = await house_management.import_assignments()
# Filter to incomplete, and today # Filter to incomplete, and today
def valid_filter(a: Optional[house_management.JobAssignment]): assigns: List[house_management.JobAssignment] = [a for a in assigns if self.is_job_valid(a)]
# If it doesn't exist, it a thot
if a is None:
return False
# If its not today, we shouldn't nag
if a.job.day_of_week.lower() != dow.lower():
return False
# If it is unassigned, we can't nag
if a.assignee is None:
return False
# If its been signed off, no need to nag
if a.signer is not None:
return False
# If the brother wasn't recognized, don't try nagging
if not a.assignee.is_valid():
return False
return True
assigns: List[house_management.JobAssignment] = [a for a in assigns if valid_filter(a)]
# Now, we want to nag each person. If we don't actually know who they are, so be it. # Now, we want to nag each person. If we don't actually know who they are, so be it.
print("Nagging!") print("Reminding!")
for a in assigns: for a in assigns:
# Get the relevant slack ids # Get the relevant slack ids
assignee_ids = await identifier.lookup_brother_userids(a.assignee) assignee_ids = await identifier.lookup_brother_userids(a.assignee)
@ -92,3 +118,15 @@ class RemindJobs(slack_util.Passive):
# Take a break to ensure no double-shots # Take a break to ensure no double-shots
await asyncio.sleep(10) await asyncio.sleep(10)
class Updatinator(slack_util.Passive):
def __init__(self, wrapper_to_update: slack_util.ClientWrapper, interval_seconds: int):
self.wrapper_target = wrapper_to_update
self.interval = interval_seconds
async def run(self):
while True:
await asyncio.sleep(self.interval)
self.wrapper_target.update_channels()
self.wrapper_target.update_users()

View File

@ -27,7 +27,7 @@ Objects to represent things within a slack workspace
class User: class User:
id: str id: str
name: str name: str
real_name: str real_name: Optional[str]
email: Optional[str] email: Optional[str]
async def get_brother(self) -> Optional[scroll_util.Brother]: async def get_brother(self) -> Optional[scroll_util.Brother]:
@ -41,8 +41,6 @@ class User:
class Channel: class Channel:
id: str id: str
name: str name: str
purpose: str
members: List[User]
""" """
@ -109,7 +107,7 @@ def message_stream(slack: SlackClient) -> Generator[Event, None, None]:
# Do forever # Do forever
while True: while True:
try: try:
if slack.rtm_connect(with_team_state=True, 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(0.1) sleep(0.1)
@ -171,6 +169,8 @@ class ClientWrapper(object):
# Cache users and channels # Cache users and channels
self.users: dict = {} self.users: dict = {}
self.channels: dict = {} self.channels: dict = {}
self.update_users()
self.update_channels()
# Scheduled events handling # Scheduled events handling
def add_passive(self, per: Passive) -> None: def add_passive(self, per: Passive) -> None:
@ -281,6 +281,83 @@ class ClientWrapper(object):
return self.api_call("chat.postMessage", **kwargs) return self.api_call("chat.postMessage", **kwargs)
def update_channels(self):
"""
Queries the slack API for all current channels
"""
# Necessary because of pagination
cursor = None
# Make a new dict to use
new_dict = {}
# Iterate over results
while True:
# Set args depending on if a cursor exists
args = {"limit": 1000, "type": "public_channel,private_channel,mpim,im"}
if cursor:
args["cursor"] = cursor
channel_dicts = self.api_call("https://slack.com/api/conversations.list", **args)
# If the response is good, put its results to the dict
if channel_dicts["ok"]:
for channel_dict in channel_dicts["channels"]:
new_channel = Channel(id=channel_dict["id"],
name=channel_dict["name"])
new_dict[new_channel.id] = new_channel
# If no new channels, just give it up
if len(channel_dicts["channels"]) == 0:
break
# Otherwise, fetch the cursor
cursor = channel_dicts.get("response_metadata").get("next_cursor")
else:
print("Warning: failed to retrieve channels")
break
self.channels = new_dict
def update_users(self):
"""
Queries the slack API for all current users
"""
# Necessary because of pagination
cursor = None
while True:
# Set args depending on if a cursor exists
args = {"limit": 1000}
if cursor:
args["cursor"] = cursor
user_dicts = self.api_call("https://slack.com/api/users.list", **args)
# Make a new dict to use
new_dict = {}
# If the response is good:
if user_dicts["ok"]:
for user_dict in user_dicts["members"]:
new_user = User(id=user_dict.get("id"),
name=user_dict.get("name"),
real_name=user_dict.get("real_name"),
email=user_dict.get("profile").get("email"))
new_dict[new_user.id] = new_user
# If no new channels, just give it up
if len(user_dicts["channels"]) == 0:
break
# Otherwise, fetch the cursor
cursor = user_dicts.get("response_metadata").get("next_cursor")
else:
print("Warning: failed to retrieve channels")
break
self.users = new_dict
# Create a single instance of the client wrapper # Create a single instance of the client wrapper
_singleton = ClientWrapper(SLACK_API) _singleton = ClientWrapper(SLACK_API)

View File

@ -2,10 +2,7 @@ import re
import textwrap import textwrap
from typing import Match from typing import Match
from slackclient import SlackClient
import house_management import house_management
import identifier
import slack_util import slack_util
from scroll_util import Brother from scroll_util import Brother