Almost done
This commit is contained in:
parent
fe33a5a0e4
commit
ddb6ffac10
|
|
@ -1,31 +0,0 @@
|
||||||
from typing import Match
|
|
||||||
|
|
||||||
from slackclient import SlackClient
|
|
||||||
|
|
||||||
import slack_util
|
|
||||||
|
|
||||||
# Useful channels
|
|
||||||
GENERAL = "C0CFHPNEM"
|
|
||||||
RANDOM = "C0CFDQWUW"
|
|
||||||
COMMAND_CENTER_ID = "GCR631LQ1"
|
|
||||||
SLAVES_TO_THE_MACHINE_ID = "C9WUQBYNP"
|
|
||||||
BOTZONE = "C3BF2MFKM"
|
|
||||||
HOUSEJOBS = "CDWDDTAT0"
|
|
||||||
|
|
||||||
|
|
||||||
# Callback for telling what channel we in
|
|
||||||
async def channel_check_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
|
||||||
# Sets the users scroll
|
|
||||||
rest_of_msg = match.group(1).strip()
|
|
||||||
rest_of_msg = rest_of_msg.replace("<", "lcaret")
|
|
||||||
rest_of_msg = rest_of_msg.replace(">", "rcaret")
|
|
||||||
|
|
||||||
# Respond
|
|
||||||
response = ""
|
|
||||||
response += "Channel id: {}\n".format(msg["channel"])
|
|
||||||
response += "Escaped message: {}\n".format(rest_of_msg)
|
|
||||||
slack_util.reply(slack, msg, response)
|
|
||||||
|
|
||||||
|
|
||||||
channel_check_hook = slack_util.ChannelHook(channel_check_callback,
|
|
||||||
patterns=r"channel id\s*(.*)")
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import traceback
|
|
||||||
from typing import List, Any, AsyncGenerator, Coroutine, TypeVar
|
|
||||||
|
|
||||||
from slackclient import SlackClient # Obvious
|
|
||||||
|
|
||||||
import channel_util
|
|
||||||
import sys
|
|
||||||
import slack_util
|
|
||||||
|
|
||||||
# Read the API token
|
|
||||||
api_file = open("apitoken.txt", 'r')
|
|
||||||
SLACK_API = next(api_file).strip()
|
|
||||||
api_file.close()
|
|
||||||
|
|
||||||
# Enable to do single-threaded and have better exceptions
|
|
||||||
DEBUG_MODE = False
|
|
||||||
|
|
||||||
A, B, C = TypeVar("A"), TypeVar("B"), TypeVar("C")
|
|
||||||
|
|
||||||
|
|
||||||
async def _loud_mouth(c: Coroutine[A, B, C]) -> Coroutine[A, B, C]:
|
|
||||||
# Print exceptions as they pass through
|
|
||||||
try:
|
|
||||||
return await c
|
|
||||||
except Exception:
|
|
||||||
traceback.print_exc()
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
|
||||||
# Init slack
|
|
||||||
self.slack = SlackClient(SLACK_API)
|
|
||||||
|
|
||||||
# Hooks go regex -> callback on (slack, msg, match)
|
|
||||||
self.hooks: List[slack_util.AbsHook] = []
|
|
||||||
|
|
||||||
# Periodicals are just wrappers around an iterable, basically
|
|
||||||
self.passives: List[slack_util.Passive] = []
|
|
||||||
|
|
||||||
# Scheduled events handling
|
|
||||||
def add_passive(self, per: slack_util.Passive) -> None:
|
|
||||||
self.passives.append(per)
|
|
||||||
|
|
||||||
async def run_passives(self) -> None:
|
|
||||||
# Make a task to repeatedly spawn each event
|
|
||||||
awaitables = [p.run(self.slack) for p in self.passives]
|
|
||||||
await asyncio.gather(*awaitables)
|
|
||||||
|
|
||||||
# Message handling
|
|
||||||
def add_hook(self, hook: slack_util.AbsHook) -> None:
|
|
||||||
self.hooks.append(hook)
|
|
||||||
|
|
||||||
async def respond_messages(self) -> None:
|
|
||||||
"""
|
|
||||||
Asynchronous tasks that eternally reads and responds to messages.
|
|
||||||
"""
|
|
||||||
async for t in self.spool_tasks():
|
|
||||||
sys.stdout.flush()
|
|
||||||
if DEBUG_MODE:
|
|
||||||
await t
|
|
||||||
|
|
||||||
async def spool_tasks(self) -> AsyncGenerator[asyncio.Task, Any]:
|
|
||||||
async for msg in self.async_message_feed():
|
|
||||||
# Preprocess msg
|
|
||||||
# We only care about standard messages, not subtypes, as those usually just channel activity
|
|
||||||
if msg.get("subtype") is not None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Never deal with general, EVER!
|
|
||||||
if msg.get("channel") == channel_util.GENERAL:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Strip garbage
|
|
||||||
msg['text'] = msg['text'].strip()
|
|
||||||
print("Recv: \"{}\"".format(msg['text']))
|
|
||||||
print(msg)
|
|
||||||
|
|
||||||
# Msg is good
|
|
||||||
# Find which hook, if any, satisfies
|
|
||||||
for hook in self.hooks:
|
|
||||||
# Try invoking each
|
|
||||||
try:
|
|
||||||
# Try to make a coroutine handling the message
|
|
||||||
coro = hook.try_apply(self.slack, msg)
|
|
||||||
|
|
||||||
# If we get a coro back, then task it up and set consumption appropriately
|
|
||||||
if coro is not None:
|
|
||||||
print("Spawned task")
|
|
||||||
yield asyncio.create_task(_loud_mouth(coro))
|
|
||||||
if hook.consumes:
|
|
||||||
break
|
|
||||||
|
|
||||||
except slack_util.DeadHook:
|
|
||||||
# If a hook wants to die, let it.
|
|
||||||
self.hooks.remove(hook)
|
|
||||||
print("Done spawning tasks. Now {} running total.".format(len(asyncio.all_tasks())))
|
|
||||||
|
|
||||||
async def async_message_feed(self) -> 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)
|
|
||||||
|
|
||||||
|
|
||||||
_singleton = ClientWrapper()
|
|
||||||
|
|
||||||
|
|
||||||
def grab() -> ClientWrapper:
|
|
||||||
return _singleton
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
"""
|
"""
|
||||||
Allows users to register their user account as a specific scroll
|
Allows users to register their user account as a specific scroll
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
import shelve
|
import shelve
|
||||||
from typing import List, Match
|
from typing import List, Match
|
||||||
|
|
||||||
from slackclient import SlackClient
|
|
||||||
|
|
||||||
import scroll_util
|
import scroll_util
|
||||||
import slack_util
|
import slack_util
|
||||||
|
|
||||||
# The following db maps SLACK_USER_ID -> SCROLL_INTEGER
|
# The following db maps SLACK_USER_ID -> SCROLL_INTEGER
|
||||||
DB_NAME = "user_scrolls"
|
DB_NAME = "user_scrolls"
|
||||||
|
DB_LOCK = asyncio.Lock()
|
||||||
|
|
||||||
# Initialize the hooks
|
# Initialize the hooks
|
||||||
NON_REG_MSG = ("You currently have no scroll registered. To register, type\n"
|
NON_REG_MSG = ("You currently have no scroll registered. To register, type\n"
|
||||||
|
|
@ -19,16 +18,17 @@ 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")
|
||||||
|
|
||||||
|
|
||||||
async def identify_callback(slack, msg, match):
|
async def identify_callback(event: slack_util.Event, match: Match):
|
||||||
"""
|
"""
|
||||||
Sets the users scroll
|
Sets the users scroll
|
||||||
"""
|
"""
|
||||||
|
async with DB_LOCK:
|
||||||
with shelve.open(DB_NAME) as db:
|
with shelve.open(DB_NAME) as db:
|
||||||
# Get the query
|
# Get the query
|
||||||
query = match.group(1).strip()
|
query = match.group(1).strip()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = msg.get("user")
|
user = event.user.user_id
|
||||||
scroll = int(query)
|
scroll = int(query)
|
||||||
db[user] = scroll
|
db[user] = scroll
|
||||||
result = "Updated user {} to have scroll {}".format(user, scroll)
|
result = "Updated user {} to have scroll {}".format(user, scroll)
|
||||||
|
|
@ -36,13 +36,14 @@ async def identify_callback(slack, msg, match):
|
||||||
result = "Bad scroll: {}".format(query)
|
result = "Bad scroll: {}".format(query)
|
||||||
|
|
||||||
# Respond
|
# Respond
|
||||||
slack_util.reply(slack, msg, result)
|
slack_util.get_slack().reply(event, result)
|
||||||
|
|
||||||
|
|
||||||
async def identify_other_callback(slack: SlackClient, msg: dict, match: Match):
|
async def identify_other_callback(event: slack_util.Event, match: Match):
|
||||||
"""
|
"""
|
||||||
Sets another users scroll
|
Sets another users scroll
|
||||||
"""
|
"""
|
||||||
|
async with DB_LOCK:
|
||||||
with shelve.open(DB_NAME) as db:
|
with shelve.open(DB_NAME) as db:
|
||||||
# Get the query
|
# Get the query
|
||||||
user = match.group(1).strip()
|
user = match.group(1).strip()
|
||||||
|
|
@ -59,32 +60,34 @@ async def identify_other_callback(slack: SlackClient, msg: dict, match: Match):
|
||||||
result = "Bad scroll: {}".format(scroll_txt)
|
result = "Bad scroll: {}".format(scroll_txt)
|
||||||
|
|
||||||
# Respond
|
# Respond
|
||||||
slack_util.reply(slack, msg, result)
|
slack_util.get_slack().reply(event, result)
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
async def check_callback(slack: SlackClient, msg: dict, match: Match):
|
async def check_callback(event: slack_util.Event, match: Match):
|
||||||
"""
|
"""
|
||||||
Replies with the users current scroll assignment
|
Replies with the users current scroll assignment
|
||||||
"""
|
"""
|
||||||
|
async with DB_LOCK:
|
||||||
# Tells the user their current scroll
|
# Tells the user their current scroll
|
||||||
with shelve.open(DB_NAME) as db:
|
with shelve.open(DB_NAME) as db:
|
||||||
try:
|
try:
|
||||||
scroll = db[msg.get("user")]
|
scroll = db[event.user.user_id]
|
||||||
result = "You are currently registered with scroll {}".format(scroll)
|
result = "You are currently registered with scroll {}".format(scroll)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
result = NON_REG_MSG
|
result = NON_REG_MSG
|
||||||
slack_util.reply(slack, msg, result)
|
slack_util.get_slack().reply(event, result)
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
async def name_callback(slack, msg, match):
|
async def name_callback(event: slack_util.Event, match: Match):
|
||||||
"""
|
"""
|
||||||
Tells the user what it thinks the calling users name is.
|
Tells the user what it thinks the calling users name is.
|
||||||
"""
|
"""
|
||||||
|
async with DB_LOCK:
|
||||||
with shelve.open(DB_NAME) as db:
|
with shelve.open(DB_NAME) as db:
|
||||||
try:
|
try:
|
||||||
scroll = db[msg.get("user")]
|
scroll = db[event.user.user_id]
|
||||||
brother = scroll_util.find_by_scroll(scroll)
|
brother = scroll_util.find_by_scroll(scroll)
|
||||||
if brother:
|
if brother:
|
||||||
result = "The bot thinks your name is {}".format(brother.name)
|
result = "The bot thinks your name is {}".format(brother.name)
|
||||||
|
|
@ -94,17 +97,7 @@ async def name_callback(slack, msg, match):
|
||||||
result = NON_REG_MSG
|
result = NON_REG_MSG
|
||||||
|
|
||||||
# Respond
|
# Respond
|
||||||
slack_util.reply(slack, msg, result)
|
slack_util.get_slack().reply(event, result)
|
||||||
|
|
||||||
|
|
||||||
async def lookup_msg_brother(msg: dict) -> scroll_util.Brother:
|
|
||||||
"""
|
|
||||||
Finds the real-world name of whoever posted msg.
|
|
||||||
Utilizes their bound-scroll.
|
|
||||||
:raises BrotherNotFound:
|
|
||||||
:return: brother dict or None
|
|
||||||
"""
|
|
||||||
return await lookup_slackid_brother(msg.get("user"))
|
|
||||||
|
|
||||||
|
|
||||||
async def lookup_slackid_brother(slack_id: str) -> scroll_util.Brother:
|
async def lookup_slackid_brother(slack_id: str) -> scroll_util.Brother:
|
||||||
|
|
@ -113,6 +106,7 @@ async def lookup_slackid_brother(slack_id: str) -> scroll_util.Brother:
|
||||||
:raises BrotherNotFound:
|
:raises BrotherNotFound:
|
||||||
:return: Brother object or None
|
:return: Brother object or None
|
||||||
"""
|
"""
|
||||||
|
async with DB_LOCK:
|
||||||
with shelve.open(DB_NAME) as db:
|
with shelve.open(DB_NAME) as db:
|
||||||
try:
|
try:
|
||||||
scroll = db[slack_id]
|
scroll = db[slack_id]
|
||||||
|
|
@ -121,13 +115,14 @@ async def lookup_slackid_brother(slack_id: str) -> scroll_util.Brother:
|
||||||
raise scroll_util.BrotherNotFound("Slack id {} not tied to brother".format(slack_id))
|
raise scroll_util.BrotherNotFound("Slack id {} not tied to brother".format(slack_id))
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
:param brother: Brother to lookup scrolls for
|
:param brother: Brother to lookup scrolls for
|
||||||
:return: List of user id strings (may be empty)
|
:return: List of user id strings (may be empty)
|
||||||
"""
|
"""
|
||||||
|
async with DB_LOCK:
|
||||||
with shelve.open(DB_NAME) as db:
|
with shelve.open(DB_NAME) as db:
|
||||||
keys = db.keys()
|
keys = db.keys()
|
||||||
result = []
|
result = []
|
||||||
|
|
|
||||||
100
job_commands.py
100
job_commands.py
|
|
@ -4,8 +4,6 @@ from typing import List, Match, Callable, TypeVar, Optional, Iterable
|
||||||
from fuzzywuzzy import fuzz
|
from fuzzywuzzy import fuzz
|
||||||
from slackclient import SlackClient
|
from slackclient import SlackClient
|
||||||
|
|
||||||
import channel_util
|
|
||||||
import client_wrapper
|
|
||||||
import house_management
|
import house_management
|
||||||
import identifier
|
import identifier
|
||||||
import scroll_util
|
import scroll_util
|
||||||
|
|
@ -16,14 +14,14 @@ SHEET_ID = "1lPj9GjB00BuIq9GelOWh5GmiGsheLlowPnHLnWBvMOM"
|
||||||
MIN_RATIO = 80.0
|
MIN_RATIO = 80.0
|
||||||
|
|
||||||
|
|
||||||
def alert_user(slack: SlackClient, brother: scroll_util.Brother, saywhat: str) -> None:
|
async def alert_user(brother: scroll_util.Brother, saywhat: str) -> None:
|
||||||
"""
|
"""
|
||||||
DM a brother saying something. Wrapper around several simpler methods
|
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)
|
# 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
|
succ = False
|
||||||
for slack_id in identifier.lookup_brother_userids(brother):
|
for slack_id in await identifier.lookup_brother_userids(brother):
|
||||||
slack_util.send_message(slack, saywhat, slack_id)
|
slack_util.get_slack().send_message(saywhat, slack_id)
|
||||||
succ = True
|
succ = True
|
||||||
|
|
||||||
# Warn if we never find
|
# Warn if we never find
|
||||||
|
|
@ -31,6 +29,7 @@ def alert_user(slack: SlackClient, brother: scroll_util.Brother, saywhat: str) -
|
||||||
print("Warning: unable to find dm for brother {}".format(brother))
|
print("Warning: unable to find dm for brother {}".format(brother))
|
||||||
|
|
||||||
|
|
||||||
|
# Generic type
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -60,8 +59,7 @@ class _ModJobContext:
|
||||||
assign: house_management.JobAssignment # The job assignment to modify
|
assign: house_management.JobAssignment # The job assignment to modify
|
||||||
|
|
||||||
|
|
||||||
async def _mod_jobs(slack: SlackClient,
|
async def _mod_jobs(event: slack_util.Event,
|
||||||
msg: dict,
|
|
||||||
relevance_scorer: Callable[[house_management.JobAssignment], Optional[float]],
|
relevance_scorer: Callable[[house_management.JobAssignment], Optional[float]],
|
||||||
modifier: Callable[[_ModJobContext], None],
|
modifier: Callable[[_ModJobContext], None],
|
||||||
no_job_msg: str = None
|
no_job_msg: str = None
|
||||||
|
|
@ -72,10 +70,10 @@ async def _mod_jobs(slack: SlackClient,
|
||||||
:param modifier: Callback function to modify a job. Only called on a successful operation, and only on one job
|
:param modifier: Callback function to modify a job. Only called on a successful operation, and only on one job
|
||||||
"""
|
"""
|
||||||
# Make an error wrapper
|
# Make an error wrapper
|
||||||
verb = slack_util.VerboseWrapper(slack, msg)
|
verb = slack_util.VerboseWrapper(event)
|
||||||
|
|
||||||
# Who invoked this command?
|
# Who invoked this command?
|
||||||
signer = await verb(identifier.lookup_msg_brother(msg))
|
signer = await verb(event.user.as_user().get_brother())
|
||||||
|
|
||||||
# Get all of the assignments
|
# Get all of the assignments
|
||||||
assigns = await verb(house_management.import_assignments())
|
assigns = await verb(house_management.import_assignments())
|
||||||
|
|
@ -115,7 +113,7 @@ async def _mod_jobs(slack: SlackClient,
|
||||||
if len(closest_assigns) == 0:
|
if len(closest_assigns) == 0:
|
||||||
if no_job_msg is None:
|
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."
|
no_job_msg = "Unable to find any jobs to apply this command to. Try again with better spelling or whatever."
|
||||||
slack_util.reply(slack, msg, no_job_msg)
|
slack_util.get_slack().reply(event, no_job_msg)
|
||||||
|
|
||||||
# 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:
|
||||||
|
|
@ -125,7 +123,7 @@ async def _mod_jobs(slack: SlackClient,
|
||||||
else:
|
else:
|
||||||
# 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.reply(slack, msg, "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))
|
||||||
|
|
||||||
|
|
@ -133,7 +131,7 @@ async def _mod_jobs(slack: SlackClient,
|
||||||
pattern = r"\d+"
|
pattern = r"\d+"
|
||||||
|
|
||||||
# Make the follow up callback
|
# Make the follow up callback
|
||||||
async def foc(_slack: SlackClient, _msg: dict, _match: Match) -> None:
|
async def foc(_event: slack_util.Event, _match: Match) -> None:
|
||||||
# Get the number out
|
# Get the number out
|
||||||
index = int(_match.group(0))
|
index = int(_match.group(0))
|
||||||
|
|
||||||
|
|
@ -143,17 +141,17 @@ async def _mod_jobs(slack: SlackClient,
|
||||||
await success_callback(closest_assigns[index])
|
await success_callback(closest_assigns[index])
|
||||||
else:
|
else:
|
||||||
# They gave a bad index, or we were unable to find the assignment again.
|
# 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.")
|
slack_util.get_slack().reply(_event, "Invalid job index / job unable to be found.")
|
||||||
|
|
||||||
# Make a listener hook
|
# Make a listener hook
|
||||||
new_hook = slack_util.ReplyWaiter(foc, pattern, msg["ts"], 120)
|
new_hook = slack_util.ReplyWaiter(foc, pattern, event.message.ts, 120)
|
||||||
|
|
||||||
# Register it
|
# Register it
|
||||||
client_wrapper.grab().add_hook(new_hook)
|
slack_util.get_slack().add_hook(new_hook)
|
||||||
|
|
||||||
|
|
||||||
async def signoff_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
async def signoff_callback(event: slack_util.Event, match: Match) -> None:
|
||||||
verb = slack_util.VerboseWrapper(slack, msg)
|
verb = slack_util.VerboseWrapper(event)
|
||||||
|
|
||||||
# Find out who we are trying to sign off is
|
# Find out who we are trying to sign off is
|
||||||
signee_name = match.group(1)
|
signee_name = match.group(1)
|
||||||
|
|
@ -171,17 +169,17 @@ async def signoff_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
||||||
context.assign.signer = context.signer
|
context.assign.signer = context.signer
|
||||||
|
|
||||||
# Say we did it wooo!
|
# Say we did it wooo!
|
||||||
slack_util.reply(slack, msg, "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(slack, 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()))
|
||||||
|
|
||||||
# Fire it off
|
# Fire it off
|
||||||
await _mod_jobs(slack, msg, scorer, modifier)
|
await _mod_jobs(event, scorer, modifier)
|
||||||
|
|
||||||
|
|
||||||
async def undo_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
async def undo_callback(event: slack_util.Event, match: Match) -> None:
|
||||||
verb = slack_util.VerboseWrapper(slack, msg)
|
verb = slack_util.VerboseWrapper(event)
|
||||||
|
|
||||||
# Find out who we are trying to sign off is
|
# Find out who we are trying to sign off is
|
||||||
signee_name = match.group(1)
|
signee_name = match.group(1)
|
||||||
|
|
@ -199,18 +197,18 @@ async def undo_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
||||||
context.assign.signer = None
|
context.assign.signer = None
|
||||||
|
|
||||||
# Say we did it wooo!
|
# Say we did it wooo!
|
||||||
slack_util.reply(slack, msg, "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(slack, 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()))
|
||||||
|
|
||||||
# Fire it off
|
# Fire it off
|
||||||
await _mod_jobs(slack, msg, scorer, modifier)
|
await _mod_jobs(event, scorer, modifier)
|
||||||
|
|
||||||
|
|
||||||
async def late_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
async def late_callback(event: slack_util.Event, match: Match) -> None:
|
||||||
verb = slack_util.VerboseWrapper(slack, msg)
|
verb = slack_util.VerboseWrapper(event)
|
||||||
|
|
||||||
# Find out who we are trying to sign off is
|
# Find out who we are trying to sign off is
|
||||||
signee_name = match.group(1)
|
signee_name = match.group(1)
|
||||||
|
|
@ -228,16 +226,16 @@ async def late_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
||||||
context.assign.late = not context.assign.late
|
context.assign.late = not context.assign.late
|
||||||
|
|
||||||
# Say we did it
|
# Say we did it
|
||||||
slack_util.reply(slack, msg, "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(slack, msg, scorer, modifier)
|
await _mod_jobs(event, scorer, modifier)
|
||||||
|
|
||||||
|
|
||||||
async def reassign_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
async def reassign_callback(event: slack_util.Event, match: Match) -> None:
|
||||||
verb = slack_util.VerboseWrapper(slack, msg)
|
verb = slack_util.VerboseWrapper(event)
|
||||||
|
|
||||||
# Find out our two targets
|
# Find out our two targets
|
||||||
from_name = match.group(1).strip()
|
from_name = match.group(1).strip()
|
||||||
|
|
@ -263,21 +261,21 @@ async def reassign_callback(slack: SlackClient, msg: dict, match: Match) -> None
|
||||||
reassign_msg = "Job {} reassigned from {} to {}".format(context.assign.job.pretty_fmt(),
|
reassign_msg = "Job {} reassigned from {} to {}".format(context.assign.job.pretty_fmt(),
|
||||||
from_bro,
|
from_bro,
|
||||||
to_bro)
|
to_bro)
|
||||||
slack_util.reply(slack, msg, reassign_msg)
|
slack_util.get_slack().reply(event, reassign_msg)
|
||||||
|
|
||||||
# Tell the people
|
# Tell the people
|
||||||
reassign_msg = "Job {} reassigned from {} to {}".format(context.assign.job.pretty_fmt(),
|
reassign_msg = "Job {} reassigned from {} to {}".format(context.assign.job.pretty_fmt(),
|
||||||
from_bro,
|
from_bro,
|
||||||
to_bro)
|
to_bro)
|
||||||
alert_user(slack, from_bro, reassign_msg)
|
alert_user(from_bro, reassign_msg)
|
||||||
alert_user(slack, to_bro, reassign_msg)
|
alert_user(to_bro, reassign_msg)
|
||||||
|
|
||||||
# Fire it off
|
# Fire it off
|
||||||
await _mod_jobs(slack, msg, scorer, modifier)
|
await _mod_jobs(event, scorer, modifier)
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
async def reset_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
async def reset_callback(event: slack_util.Event, match: Match) -> None:
|
||||||
"""
|
"""
|
||||||
Resets the scores.
|
Resets the scores.
|
||||||
"""
|
"""
|
||||||
|
|
@ -299,18 +297,18 @@ async def reset_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
||||||
house_management.apply_house_points(points, await house_management.import_assignments())
|
house_management.apply_house_points(points, await house_management.import_assignments())
|
||||||
house_management.export_points(headers, points)
|
house_management.export_points(headers, points)
|
||||||
|
|
||||||
slack_util.reply(slack, msg, "Reset scores and signoffs")
|
slack_util.get_slack().reply(event, "Reset scores and signoffs")
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
async def refresh_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
async def refresh_callback(event: slack_util.Event, match: Match) -> None:
|
||||||
headers, points = await house_management.import_points()
|
headers, points = await house_management.import_points()
|
||||||
house_management.apply_house_points(points, await house_management.import_assignments())
|
house_management.apply_house_points(points, await house_management.import_assignments())
|
||||||
house_management.export_points(headers, points)
|
house_management.export_points(headers, points)
|
||||||
slack_util.reply(slack, msg, "Force updated point values")
|
slack_util.get_slack().reply(event, "Force updated point values")
|
||||||
|
|
||||||
|
|
||||||
async def nag_callback(slack, msg, match):
|
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()
|
||||||
|
|
||||||
|
|
@ -325,7 +323,7 @@ async def nag_callback(slack, msg, match):
|
||||||
|
|
||||||
# If no jobs found, somethings up. Probably mispelled day.
|
# If no jobs found, somethings up. Probably mispelled day.
|
||||||
if not assigns:
|
if not assigns:
|
||||||
slack_util.reply(slack, msg, "No jobs found. Check that the day is spelled correctly, with no extra symbols.\n"
|
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.",
|
"It is possible that all jobs have been signed off, as well.",
|
||||||
in_thread=True)
|
in_thread=True)
|
||||||
return
|
return
|
||||||
|
|
@ -339,7 +337,7 @@ async def nag_callback(slack, msg, match):
|
||||||
response += "({}) {} -- {} ".format(assign.job.house, assign.job.name, assign.assignee.name)
|
response += "({}) {} -- {} ".format(assign.job.house, assign.job.name, assign.assignee.name)
|
||||||
|
|
||||||
# Find the people to @
|
# Find the people to @
|
||||||
brother_slack_ids = identifier.lookup_brother_userids(assign.assignee)
|
brother_slack_ids = await identifier.lookup_brother_userids(assign.assignee)
|
||||||
|
|
||||||
if brother_slack_ids:
|
if brother_slack_ids:
|
||||||
for slack_id in brother_slack_ids:
|
for slack_id in brother_slack_ids:
|
||||||
|
|
@ -348,7 +346,8 @@ async def nag_callback(slack, msg, match):
|
||||||
response += "(scroll missing. Please register for @ pings!)"
|
response += "(scroll missing. Please register for @ pings!)"
|
||||||
response += "\n"
|
response += "\n"
|
||||||
|
|
||||||
slack_util.reply(slack, msg, response, in_thread=False, to_channel=channel_util.GENERAL)
|
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)
|
||||||
|
|
||||||
|
|
||||||
signoff_hook = slack_util.ChannelHook(signoff_callback,
|
signoff_hook = slack_util.ChannelHook(signoff_callback,
|
||||||
|
|
@ -356,7 +355,7 @@ signoff_hook = slack_util.ChannelHook(signoff_callback,
|
||||||
r"signoff\s+(.*)",
|
r"signoff\s+(.*)",
|
||||||
r"sign off\s+(.*)",
|
r"sign off\s+(.*)",
|
||||||
],
|
],
|
||||||
channel_whitelist=[channel_util.HOUSEJOBS])
|
channel_whitelist=["#housejobs"])
|
||||||
|
|
||||||
undo_hook = slack_util.ChannelHook(undo_callback,
|
undo_hook = slack_util.ChannelHook(undo_callback,
|
||||||
patterns=[
|
patterns=[
|
||||||
|
|
@ -364,37 +363,36 @@ undo_hook = slack_util.ChannelHook(undo_callback,
|
||||||
r"undosignoff\s+(.*)",
|
r"undosignoff\s+(.*)",
|
||||||
r"undo signoff\s+(.*)",
|
r"undo signoff\s+(.*)",
|
||||||
],
|
],
|
||||||
channel_whitelist=[channel_util.HOUSEJOBS])
|
channel_whitelist=["#housejobs"])
|
||||||
|
|
||||||
late_hook = slack_util.ChannelHook(late_callback,
|
late_hook = slack_util.ChannelHook(late_callback,
|
||||||
patterns=[
|
patterns=[
|
||||||
r"marklate\s+(.*)",
|
r"marklate\s+(.*)",
|
||||||
r"mark late\s+(.*)",
|
r"mark late\s+(.*)",
|
||||||
],
|
],
|
||||||
channel_whitelist=[channel_util.HOUSEJOBS])
|
channel_whitelist=["#housejobs"])
|
||||||
|
|
||||||
reset_hook = slack_util.ChannelHook(reset_callback,
|
reset_hook = slack_util.ChannelHook(reset_callback,
|
||||||
patterns=[
|
patterns=[
|
||||||
r"reset signoffs",
|
r"reset signoffs",
|
||||||
r"reset sign offs",
|
r"reset sign offs",
|
||||||
],
|
],
|
||||||
channel_whitelist=[channel_util.COMMAND_CENTER_ID])
|
channel_whitelist=["#command-center"])
|
||||||
|
|
||||||
nag_hook = slack_util.ChannelHook(nag_callback,
|
nag_hook = slack_util.ChannelHook(nag_callback,
|
||||||
patterns=[
|
patterns=[
|
||||||
r"nagjobs\s+(.*)",
|
r"nagjobs\s+(.*)",
|
||||||
r"nag jobs\s+(.*)"
|
r"nag jobs\s+(.*)"
|
||||||
],
|
],
|
||||||
channel_whitelist=[channel_util.COMMAND_CENTER_ID])
|
channel_whitelist=["#command-center"])
|
||||||
|
|
||||||
reassign_hook = slack_util.ChannelHook(reassign_callback,
|
reassign_hook = slack_util.ChannelHook(reassign_callback,
|
||||||
patterns=r"reassign\s+(.*?)->\s+(.+)",
|
patterns=r"reassign\s+(.*?)->\s+(.+)",
|
||||||
channel_whitelist=[channel_util.HOUSEJOBS])
|
channel_whitelist=["#housejobs"])
|
||||||
|
|
||||||
refresh_hook = slack_util.ChannelHook(refresh_callback,
|
refresh_hook = slack_util.ChannelHook(refresh_callback,
|
||||||
patterns=[
|
patterns=[
|
||||||
"refresh points",
|
"refresh points",
|
||||||
"update points"
|
"update points"
|
||||||
],
|
],
|
||||||
channel_whitelist=[channel_util.COMMAND_CENTER_ID]
|
channel_whitelist=["#command-center"])
|
||||||
)
|
|
||||||
|
|
|
||||||
15
main.py
15
main.py
|
|
@ -2,10 +2,6 @@ import asyncio
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Match
|
from typing import Match
|
||||||
|
|
||||||
from slackclient import SlackClient
|
|
||||||
|
|
||||||
import channel_util
|
|
||||||
import client_wrapper
|
|
||||||
import identifier
|
import identifier
|
||||||
import job_commands
|
import job_commands
|
||||||
import management_commands
|
import management_commands
|
||||||
|
|
@ -16,7 +12,7 @@ import slavestothemachine
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
wrap = client_wrapper.grab()
|
wrap = slack_util.get_slack()
|
||||||
|
|
||||||
# Add scroll handling
|
# Add scroll handling
|
||||||
wrap.add_hook(scroll_util.scroll_hook)
|
wrap.add_hook(scroll_util.scroll_hook)
|
||||||
|
|
@ -27,9 +23,6 @@ def main() -> None:
|
||||||
wrap.add_hook(identifier.identify_other_hook)
|
wrap.add_hook(identifier.identify_other_hook)
|
||||||
wrap.add_hook(identifier.name_hook)
|
wrap.add_hook(identifier.name_hook)
|
||||||
|
|
||||||
# Added channel utility
|
|
||||||
wrap.add_hook(channel_util.channel_check_hook)
|
|
||||||
|
|
||||||
# Add kill switch
|
# Add kill switch
|
||||||
wrap.add_hook(management_commands.reboot_hook)
|
wrap.add_hook(management_commands.reboot_hook)
|
||||||
|
|
||||||
|
|
@ -55,7 +48,7 @@ def main() -> None:
|
||||||
wrap.add_passive(periodicals.RemindJobs())
|
wrap.add_passive(periodicals.RemindJobs())
|
||||||
|
|
||||||
event_loop = asyncio.get_event_loop()
|
event_loop = asyncio.get_event_loop()
|
||||||
event_loop.set_debug(client_wrapper.DEBUG_MODE)
|
event_loop.set_debug(slack_util.DEBUG_MODE)
|
||||||
message_handling = wrap.respond_messages()
|
message_handling = wrap.respond_messages()
|
||||||
passive_handling = wrap.run_passives()
|
passive_handling = wrap.run_passives()
|
||||||
both = asyncio.gather(message_handling, passive_handling)
|
both = asyncio.gather(message_handling, passive_handling)
|
||||||
|
|
@ -63,8 +56,8 @@ def main() -> None:
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
async def help_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
async def help_callback(event: slack_util.Event, match: Match) -> None:
|
||||||
slack_util.reply(slack, msg, textwrap.dedent("""
|
slack_util.get_slack().reply(event, textwrap.dedent("""
|
||||||
Commands are as follows. Note that some only work in certain channels.
|
Commands are as follows. Note that some only work in certain channels.
|
||||||
"my scroll is number" : Registers your slack account to have a certain scroll, for the purpose of automatic dm's.
|
"my scroll is number" : Registers your slack account to have a certain scroll, for the purpose of automatic dm's.
|
||||||
"@person has scroll number" : same as above, but for other users. Helpful if they are being obstinate.
|
"@person has scroll number" : same as above, but for other users. Helpful if they are being obstinate.
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,25 @@
|
||||||
from typing import Match, List
|
from typing import Match, List
|
||||||
|
|
||||||
from slackclient import SlackClient
|
|
||||||
|
|
||||||
import channel_util
|
|
||||||
import slack_util
|
import slack_util
|
||||||
|
|
||||||
|
|
||||||
def list_hooks_callback_gen(hooks: List[slack_util.ChannelHook]) -> slack_util.Callback:
|
def list_hooks_callback_gen(hooks: List[slack_util.ChannelHook]) -> slack_util.Callback:
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
async def callback(slack, msg, match):
|
async def callback(event: slack_util.Event, match: Match) -> None:
|
||||||
slack_util.reply(slack, msg, "\n".join(hook.patterns for hook in hooks))
|
slack_util.get_slack().reply(event, "\n".join(hook.patterns for hook in hooks))
|
||||||
|
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
|
|
||||||
# Gracefully reboot to reload code changes
|
# Gracefully reboot to reload code changes
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
async def reboot_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
async def reboot_callback(event: slack_util.Event, match: Match) -> None:
|
||||||
response = "Ok. Rebooting..."
|
response = "Ok. Rebooting..."
|
||||||
slack_util.reply(slack, msg, response)
|
slack_util.get_slack().reply(event, response)
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
# Make hooks
|
# Make hooks
|
||||||
reboot_hook = slack_util.ChannelHook(reboot_callback,
|
reboot_hook = slack_util.ChannelHook(reboot_callback,
|
||||||
patterns=r"reboot",
|
patterns=r"reboot",
|
||||||
channel_whitelist=[channel_util.COMMAND_CENTER_ID])
|
channel_whitelist=["#command-center"])
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,6 @@ import asyncio
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
|
||||||
from slackclient import SlackClient
|
|
||||||
|
|
||||||
import channel_util
|
|
||||||
import house_management
|
import house_management
|
||||||
import identifier
|
import identifier
|
||||||
import slack_util
|
import slack_util
|
||||||
|
|
@ -17,7 +14,7 @@ def seconds_until(target: datetime) -> float:
|
||||||
|
|
||||||
|
|
||||||
class ItsTenPM(slack_util.Passive):
|
class ItsTenPM(slack_util.Passive):
|
||||||
async def run(self, slack: SlackClient) -> None:
|
async def run(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
# Get 10PM
|
# Get 10PM
|
||||||
ten_pm = datetime.now().replace(hour=22, minute=0, second=0)
|
ten_pm = datetime.now().replace(hour=22, minute=0, second=0)
|
||||||
|
|
@ -27,14 +24,14 @@ class ItsTenPM(slack_util.Passive):
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
|
|
||||||
# Crow like a rooster
|
# Crow like a rooster
|
||||||
slack_util.send_message(slack, "IT'S 10 PM!", channel_util.RANDOM)
|
slack_util.get_slack().send_message("IT'S 10 PM!", slack_util.get_slack().get_channel_by_name("#random").id)
|
||||||
|
|
||||||
# Wait a while before trying it again, to prevent duplicates
|
# Wait a while before trying it again, to prevent duplicates
|
||||||
await asyncio.sleep(60)
|
await asyncio.sleep(60)
|
||||||
|
|
||||||
|
|
||||||
class RemindJobs(slack_util.Passive):
|
class RemindJobs(slack_util.Passive):
|
||||||
async def run(self, slack: SlackClient) -> 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)
|
||||||
|
|
@ -80,14 +77,14 @@ class RemindJobs(slack_util.Passive):
|
||||||
print("Nagging!")
|
print("Nagging!")
|
||||||
for a in assigns:
|
for a in assigns:
|
||||||
# Get the relevant slack ids
|
# Get the relevant slack ids
|
||||||
assignee_ids = identifier.lookup_brother_userids(a.assignee)
|
assignee_ids = await identifier.lookup_brother_userids(a.assignee)
|
||||||
|
|
||||||
# For each, send them a DM
|
# For each, send them a DM
|
||||||
success = False
|
success = False
|
||||||
for slack_id in assignee_ids:
|
for slack_id in assignee_ids:
|
||||||
msg = "{}, you still need to do {}".format(a.assignee.name, a.job.pretty_fmt())
|
msg = "{}, you still need to do {}".format(a.assignee.name, a.job.pretty_fmt())
|
||||||
success = True
|
success = True
|
||||||
slack_util.send_message(slack, msg, slack_id)
|
slack_util.get_slack().send_message(msg, slack_id)
|
||||||
|
|
||||||
# Warn on failure
|
# Warn on failure
|
||||||
if not success:
|
if not success:
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ from dataclasses import dataclass
|
||||||
from typing import List, Optional, Match
|
from typing import List, Optional, Match
|
||||||
|
|
||||||
from fuzzywuzzy import process
|
from fuzzywuzzy import process
|
||||||
from slackclient import SlackClient
|
|
||||||
|
|
||||||
import slack_util
|
import slack_util
|
||||||
|
|
||||||
|
|
@ -38,7 +37,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]
|
||||||
|
|
||||||
|
|
||||||
async def scroll_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
async def scroll_callback(event: slack_util.Event, 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.
|
||||||
"""
|
"""
|
||||||
|
|
@ -57,7 +56,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)
|
slack_util.get_slack().reply(event, result)
|
||||||
|
|
||||||
|
|
||||||
def find_by_scroll(scroll: int) -> Optional[Brother]:
|
def find_by_scroll(scroll: int) -> Optional[Brother]:
|
||||||
|
|
|
||||||
363
slack_util.py
363
slack_util.py
|
|
@ -1,71 +1,61 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from time import sleep, time
|
from time import sleep, time
|
||||||
from typing import Any, Optional, Generator, Match, Callable, List, Coroutine, Union, TypeVar, Awaitable
|
from typing import List, Any, AsyncGenerator, Coroutine, TypeVar
|
||||||
|
from typing import Optional, Generator, Match, Callable, Union, Awaitable
|
||||||
|
|
||||||
from slackclient import SlackClient
|
from slackclient import SlackClient
|
||||||
from slackclient.client import SlackNotConnected
|
from slackclient.client import SlackNotConnected
|
||||||
|
|
||||||
|
# Enable to do single-threaded and have better exceptions
|
||||||
|
import identifier
|
||||||
|
import scroll_util
|
||||||
|
|
||||||
|
DEBUG_MODE = False
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Slack helpers. Separated for compartmentalization
|
Objects to represent things within a slack workspace
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def reply(msg: dict, text: str, in_thread: bool = True, to_channel: str = None) -> dict:
|
@dataclass
|
||||||
"""
|
|
||||||
Sends message with "text" as its content to the channel that message came from.
|
|
||||||
Returns the JSON response.
|
|
||||||
"""
|
|
||||||
# If no channel specified, just do same as msg
|
|
||||||
if to_channel is None:
|
|
||||||
to_channel = msg['channel']
|
|
||||||
|
|
||||||
# Send in a thread by default
|
|
||||||
if in_thread:
|
|
||||||
thread = (msg.get("thread_ts") # In-thread case - get parent ts
|
|
||||||
or msg.get("ts")) # Not in-thread case - get msg itself ts
|
|
||||||
return send_message(slack, text, to_channel, thread=thread)
|
|
||||||
else:
|
|
||||||
return send_message(slack, text, to_channel)
|
|
||||||
|
|
||||||
|
|
||||||
def send_message(text: str, channel: str, thread: str = None, broadcast: bool = False) -> dict:
|
|
||||||
"""
|
|
||||||
Copy of the internal send message function of slack, with some helpful options.
|
|
||||||
Returns the JSON response.
|
|
||||||
"""
|
|
||||||
kwargs = {"channel": channel, "text": text}
|
|
||||||
if thread:
|
|
||||||
kwargs["thread_ts"] = thread
|
|
||||||
if broadcast:
|
|
||||||
kwargs["reply_broadcast"] = True
|
|
||||||
|
|
||||||
return slack.api_call("chat.postMessage", **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Objects to represent things
|
|
||||||
"""
|
|
||||||
class User:
|
class User:
|
||||||
pass
|
id: str
|
||||||
|
name: str
|
||||||
|
real_name: str
|
||||||
|
email: Optional[str]
|
||||||
|
|
||||||
|
async def get_brother(self) -> Optional[scroll_util.Brother]:
|
||||||
|
"""
|
||||||
|
Try to find the brother corresponding to this user.
|
||||||
|
"""
|
||||||
|
return await identifier.lookup_slackid_brother(self.id)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Channel:
|
class Channel:
|
||||||
@property
|
id: str
|
||||||
def channel_name(self) -> str:
|
name: str
|
||||||
raise NotImplementedError()
|
purpose: str
|
||||||
|
members: List[User]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Below we have a modular system that represents possible event contents.
|
Objects to represent attributes an event may contain
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Event:
|
class Event:
|
||||||
channel: Optional[ChannelContext]
|
channel: Optional[ChannelContext] = None
|
||||||
user: Optional[UserContext]
|
user: Optional[UserContext] = None
|
||||||
message: Optional[Message]
|
message: Optional[MessageContext] = None
|
||||||
|
thread: Optional[ThreadContext] = None
|
||||||
|
|
||||||
|
|
||||||
# If this was posted in a specific channel or conversation
|
# If this was posted in a specific channel or conversation
|
||||||
|
|
@ -73,6 +63,9 @@ class Event:
|
||||||
class ChannelContext:
|
class ChannelContext:
|
||||||
channel_id: str
|
channel_id: str
|
||||||
|
|
||||||
|
def get_channel(self) -> Channel:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
# If there is a specific user associated with this event
|
# If there is a specific user associated with this event
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -85,16 +78,28 @@ class UserContext:
|
||||||
|
|
||||||
# Whether or not this is a threadable text message
|
# Whether or not this is a threadable text message
|
||||||
@dataclass
|
@dataclass
|
||||||
class Message:
|
class MessageContext:
|
||||||
ts: str
|
ts: str
|
||||||
text: str
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ThreadContext:
|
||||||
|
thread_ts: str
|
||||||
|
parent_ts: str
|
||||||
|
|
||||||
|
|
||||||
|
# If a file was additionally shared
|
||||||
@dataclass
|
@dataclass
|
||||||
class File:
|
class File:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Objects for interfacing easily with rtm steams
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def message_stream(slack: SlackClient) -> Generator[Event, None, None]:
|
def message_stream(slack: SlackClient) -> Generator[Event, None, None]:
|
||||||
"""
|
"""
|
||||||
Generator that yields messages from slack.
|
Generator that yields messages from slack.
|
||||||
|
|
@ -104,50 +109,199 @@ 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=False, auto_reconnect=True):
|
if slack.rtm_connect(with_team_state=True, auto_reconnect=True):
|
||||||
print("Waiting for messages")
|
print("Waiting for messages")
|
||||||
while True:
|
while True:
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
update = slack.rtm_read()
|
update_list = slack.rtm_read()
|
||||||
for item in update:
|
|
||||||
if item.get('type') == 'message':
|
# Handle each
|
||||||
yield item
|
for update in update_list:
|
||||||
|
print("Message received: {}".format(update))
|
||||||
|
event = Event()
|
||||||
|
|
||||||
|
# Big logic folks
|
||||||
|
if update["type"] == "message":
|
||||||
|
event.message = MessageContext(update["ts"], update["text"])
|
||||||
|
event.channel = ChannelContext(update["channel"])
|
||||||
|
event.user = UserContext(update["user"])
|
||||||
|
|
||||||
|
# TODO: Handle more types
|
||||||
|
# We need to
|
||||||
|
|
||||||
|
yield event
|
||||||
|
|
||||||
except (SlackNotConnected, OSError) as e:
|
except (SlackNotConnected, OSError) as e:
|
||||||
print("Error while reading messages:")
|
print("Error while reading messages:")
|
||||||
print(e)
|
print(e)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError) as e:
|
||||||
print("Malformed message... Restarting connection")
|
print("Malformed message... Restarting connection")
|
||||||
|
print(e)
|
||||||
|
|
||||||
sleep(5)
|
sleep(5)
|
||||||
print("Connection failed - retrying")
|
print("Connection failed - retrying")
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
"""
|
||||||
|
Objects to wrap slack connections
|
||||||
|
"""
|
||||||
|
# Read the API token
|
||||||
|
api_file = open("apitoken.txt", 'r')
|
||||||
|
SLACK_API = next(api_file).strip()
|
||||||
|
api_file.close()
|
||||||
|
|
||||||
|
|
||||||
class VerboseWrapper(Callable):
|
class ClientWrapper(object):
|
||||||
"""
|
"""
|
||||||
Generates exception-ready delegates.
|
Essentially the main state object.
|
||||||
Warns of exceptions as they are passed through it, via responding to the given message.
|
We only ever expect one of these per api token.
|
||||||
|
Holds a slack client, and handles messsages.
|
||||||
"""
|
"""
|
||||||
def __init__(self, slack: SlackClient, command_msg: dict):
|
|
||||||
self.slack = slack
|
|
||||||
self.command_msg = command_msg
|
|
||||||
|
|
||||||
async def __call__(self, awt: Awaitable[T]) -> T:
|
def __init__(self, api_token):
|
||||||
|
# Init slack
|
||||||
|
self.slack = SlackClient(api_token)
|
||||||
|
|
||||||
|
# Hooks go regex -> callback on (slack, msg, match)
|
||||||
|
self.hooks: List[AbsHook] = []
|
||||||
|
|
||||||
|
# Periodicals are just wrappers around an iterable, basically
|
||||||
|
self.passives: List[Passive] = []
|
||||||
|
|
||||||
|
# Cache users and channels
|
||||||
|
self.users: dict = {}
|
||||||
|
self.channels: dict = {}
|
||||||
|
|
||||||
|
# Scheduled events handling
|
||||||
|
def add_passive(self, per: Passive) -> None:
|
||||||
|
self.passives.append(per)
|
||||||
|
|
||||||
|
async def run_passives(self) -> None:
|
||||||
|
"""
|
||||||
|
Run all currently added passives
|
||||||
|
"""
|
||||||
|
awaitables = [p.run() for p in self.passives]
|
||||||
|
await asyncio.gather(*awaitables)
|
||||||
|
|
||||||
|
# Message handling
|
||||||
|
def add_hook(self, hook: AbsHook) -> None:
|
||||||
|
self.hooks.append(hook)
|
||||||
|
|
||||||
|
async def respond_messages(self) -> None:
|
||||||
|
"""
|
||||||
|
Asynchronous tasks that eternally reads and responds to messages.
|
||||||
|
"""
|
||||||
|
async for t in self.spool_tasks():
|
||||||
|
sys.stdout.flush()
|
||||||
|
if DEBUG_MODE:
|
||||||
|
await t
|
||||||
|
|
||||||
|
async def spool_tasks(self) -> AsyncGenerator[asyncio.Task, Any]:
|
||||||
|
async for event in self.async_event_feed():
|
||||||
|
# Find which hook, if any, satisfies
|
||||||
|
for hook in list(self.hooks): # Note that we do list(self.hooks) to avoid edit-while-iterating issues
|
||||||
|
# Try invoking each
|
||||||
try:
|
try:
|
||||||
return await awt
|
# Try to make a coroutine handling the message
|
||||||
except Exception as e:
|
coro = hook.try_apply(event)
|
||||||
reply(self.command_msg, "Error: {}".format(str(e)), True)
|
|
||||||
raise e
|
# If we get a coro back, then task it up and set consumption appropriately
|
||||||
|
if coro is not None:
|
||||||
|
print("Spawned task")
|
||||||
|
yield asyncio.create_task(_exception_printing_task(coro))
|
||||||
|
if hook.consumes:
|
||||||
|
break
|
||||||
|
|
||||||
|
except DeadHook:
|
||||||
|
# If a hook wants to die, let it.
|
||||||
|
self.hooks.remove(hook)
|
||||||
|
print("Done spawning tasks. Now {} running total.".format(len(asyncio.all_tasks())))
|
||||||
|
|
||||||
|
async def async_event_feed(self) -> AsyncGenerator[Event, None]:
|
||||||
|
"""
|
||||||
|
Async wrapper around the message feed.
|
||||||
|
Yields messages awaitably forever.
|
||||||
|
"""
|
||||||
|
# Create the msg feed
|
||||||
|
feed = 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)
|
||||||
|
|
||||||
|
def get_channel(self, channel_id: str) -> Optional[Channel]:
|
||||||
|
return self.channels.get(channel_id)
|
||||||
|
|
||||||
|
def get_channel_by_name(self, channel_name: str) -> Optional[Channel]:
|
||||||
|
# Find the channel in the dict
|
||||||
|
for v in self.channels.values():
|
||||||
|
if v.name == channel_name:
|
||||||
|
return v
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_user(self, user_id: str) -> Optional[Channel]:
|
||||||
|
return self.users.get(user_id)
|
||||||
|
|
||||||
|
def api_call(self, api_method, **kwargs):
|
||||||
|
return self.slack.api_call(api_method, **kwargs)
|
||||||
|
|
||||||
|
def reply(self, event: Event, text: str, in_thread: bool = True) -> dict:
|
||||||
|
"""
|
||||||
|
Replies to a message.
|
||||||
|
Message must have a channel and message context.
|
||||||
|
Returns the JSON response.
|
||||||
|
"""
|
||||||
|
# Ensure we're actually replying to a valid message
|
||||||
|
assert (event.channel and event.message) is not None
|
||||||
|
|
||||||
|
# Send in a thread by default
|
||||||
|
if in_thread:
|
||||||
|
# Figure otu what thread to send it to
|
||||||
|
thread = event.message.ts
|
||||||
|
if event.thread:
|
||||||
|
thread = event.thread.thread_ts
|
||||||
|
return self.send_message(text, event.channel.channel_id, thread=thread)
|
||||||
|
else:
|
||||||
|
return self.send_message(text, event.channel.channel_id)
|
||||||
|
|
||||||
|
def send_message(self, text: str, channel_id: str, thread: str = None, broadcast: bool = False) -> dict:
|
||||||
|
"""
|
||||||
|
Copy of the internal send message function of slack, with some helpful options.
|
||||||
|
Returns the JSON response.
|
||||||
|
"""
|
||||||
|
kwargs = {"channel": channel_id, "text": text}
|
||||||
|
if thread:
|
||||||
|
kwargs["thread_ts"] = thread
|
||||||
|
if broadcast:
|
||||||
|
kwargs["reply_broadcast"] = True
|
||||||
|
|
||||||
|
return self.api_call("chat.postMessage", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# The result of a message
|
# Create a single instance of the client wrapper
|
||||||
|
_singleton = ClientWrapper(SLACK_API)
|
||||||
|
|
||||||
|
|
||||||
|
def get_slack() -> ClientWrapper:
|
||||||
|
return _singleton
|
||||||
|
|
||||||
|
|
||||||
|
# Return type of an event callback
|
||||||
MsgAction = Coroutine[Any, Any, None]
|
MsgAction = Coroutine[Any, Any, None]
|
||||||
# The function called on a message
|
|
||||||
Callback = Callable[[SlackClient, Message, Match], MsgAction]
|
# Type signature of an event callback function
|
||||||
|
Callback = Callable[[Event, Match], MsgAction]
|
||||||
|
|
||||||
|
"""
|
||||||
|
Hooks
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Signal exception to be raised when a hook has died
|
||||||
class DeadHook(Exception):
|
class DeadHook(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -166,6 +320,7 @@ class ChannelHook(AbsHook):
|
||||||
"""
|
"""
|
||||||
Hook that handles messages in a variety of channels
|
Hook that handles messages in a variety of channels
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
callback: Callback,
|
callback: Callback,
|
||||||
patterns: Union[str, List[str]],
|
patterns: Union[str, List[str]],
|
||||||
|
|
@ -185,8 +340,7 @@ class ChannelHook(AbsHook):
|
||||||
|
|
||||||
# Remedy some sensible defaults
|
# Remedy some sensible defaults
|
||||||
if self.channel_blacklist is None:
|
if self.channel_blacklist is None:
|
||||||
import channel_util
|
self.channel_blacklist = ["#general"]
|
||||||
self.channel_blacklist = [channel_util.GENERAL]
|
|
||||||
elif self.channel_whitelist is None:
|
elif self.channel_whitelist is None:
|
||||||
pass # We leave as none to show no whitelisting in effect
|
pass # We leave as none to show no whitelisting in effect
|
||||||
else:
|
else:
|
||||||
|
|
@ -196,25 +350,32 @@ class ChannelHook(AbsHook):
|
||||||
"""
|
"""
|
||||||
Returns whether a message should be handled by this dict, returning a Match if so, or None
|
Returns whether a message should be handled by this dict, returning a Match if so, or None
|
||||||
"""
|
"""
|
||||||
|
# Ensure that this is an event in a specific channel, with a text component
|
||||||
|
if not (event.channel and event.message):
|
||||||
|
return None
|
||||||
|
|
||||||
# Fail if pattern invalid
|
# Fail if pattern invalid
|
||||||
match = None
|
match = None
|
||||||
for p in self.patterns:
|
for p in self.patterns:
|
||||||
match = re.match(p, msg['text'], flags=re.IGNORECASE)
|
match = re.match(p, event.message.text.strip(), flags=re.IGNORECASE)
|
||||||
if match is not None:
|
if match is not None:
|
||||||
break
|
break
|
||||||
|
|
||||||
if match is None:
|
if match is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Get the channel name
|
||||||
|
channel_name = event.channel.get_channel().name
|
||||||
|
|
||||||
# Fail if whitelist defined, and we aren't there
|
# Fail if whitelist defined, and we aren't there
|
||||||
if self.channel_whitelist is not None and msg["channel"] not in self.channel_whitelist:
|
if self.channel_whitelist is not None and channel_name not in self.channel_whitelist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Fail if blacklist defined, and we are there
|
# Fail if blacklist defined, and we are there
|
||||||
if self.channel_blacklist is not None and msg["channel"] in self.channel_blacklist:
|
if self.channel_blacklist is not None and channel_name in self.channel_blacklist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self.callback(slack, msg, match)
|
return self.callback(event, match)
|
||||||
|
|
||||||
|
|
||||||
class ReplyWaiter(AbsHook):
|
class ReplyWaiter(AbsHook):
|
||||||
|
|
@ -238,19 +399,22 @@ class ReplyWaiter(AbsHook):
|
||||||
|
|
||||||
# If so, give up the ghost
|
# If so, give up the ghost
|
||||||
if self.dead or should_expire:
|
if self.dead or should_expire:
|
||||||
print("Reply waiter has expired after {} seconds".format(time_alive))
|
|
||||||
raise DeadHook()
|
raise DeadHook()
|
||||||
|
|
||||||
|
# Next make sure we're actually a message
|
||||||
|
if not (event.message and event.thread):
|
||||||
|
return None
|
||||||
|
|
||||||
# Otherwise proceed normally
|
# Otherwise proceed normally
|
||||||
# Is the msg the one we care about? If not, ignore
|
# Is the msg the one we care about? If not, ignore
|
||||||
if msg.get("thread_ts", None) != self.thread_ts:
|
if event.thread.thread_ts != self.thread_ts:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Does it match the regex? if not, ignore
|
# Does it match the regex? if not, ignore
|
||||||
match = re.match(self.pattern, msg['text'], flags=re.IGNORECASE)
|
match = re.match(self.pattern, event.message.text.strip(), flags=re.IGNORECASE)
|
||||||
if match:
|
if match:
|
||||||
self.dead = True
|
self.dead = True
|
||||||
return self.callback(slack, msg, match)
|
return self.callback(event, match)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -263,3 +427,44 @@ class Passive(object):
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
# Run this passive routed through the specified slack client.
|
# Run this passive routed through the specified slack client.
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Methods for easily responding to messages, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class VerboseWrapper(Callable):
|
||||||
|
"""
|
||||||
|
Generates exception-ready delegates.
|
||||||
|
Warns of exceptions as they are passed through it, via responding to the given message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, event: Event):
|
||||||
|
self.event = event
|
||||||
|
|
||||||
|
async def __call__(self, awt: Awaitable[T]) -> T:
|
||||||
|
try:
|
||||||
|
return await awt
|
||||||
|
except Exception as e:
|
||||||
|
get_slack().reply(self.event, "Error: {}".format(str(e)), True)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Miscellania
|
||||||
|
"""
|
||||||
|
|
||||||
|
A, B, C = TypeVar("A"), TypeVar("B"), TypeVar("C")
|
||||||
|
|
||||||
|
|
||||||
|
# Prints exceptions instead of silently dropping them in async tasks
|
||||||
|
async def _exception_printing_task(c: Coroutine[A, B, C]) -> Coroutine[A, B, C]:
|
||||||
|
# Print exceptions as they pass through
|
||||||
|
try:
|
||||||
|
return await c
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
raise
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ from typing import Match
|
||||||
|
|
||||||
from slackclient import SlackClient
|
from slackclient import SlackClient
|
||||||
|
|
||||||
import channel_util
|
|
||||||
import house_management
|
import house_management
|
||||||
import identifier
|
import identifier
|
||||||
import slack_util
|
import slack_util
|
||||||
|
|
@ -19,16 +18,16 @@ def fmt_work_dict(work_dict: dict) -> str:
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
async def count_work_callback(slack: SlackClient, msg: dict, match: Match) -> None:
|
async def count_work_callback(event: slack_util.Event, match: Match) -> None:
|
||||||
# Make an error wrapper
|
# Make an error wrapper
|
||||||
verb = slack_util.VerboseWrapper(slack, msg)
|
verb = slack_util.VerboseWrapper(event)
|
||||||
|
|
||||||
# Tidy the text
|
# Tidy the text
|
||||||
text = msg["text"].lower().strip()
|
text = event.message.text.strip()
|
||||||
|
|
||||||
# Couple things to work through.
|
# Couple things to work through.
|
||||||
# One: Who sent the message?
|
# One: Who sent the message?
|
||||||
who_wrote = await verb(identifier.lookup_msg_brother(msg))
|
who_wrote = await verb(event.user.as_user().get_brother())
|
||||||
who_wrote_label = "{} [{}]".format(who_wrote.name, who_wrote.scroll)
|
who_wrote_label = "{} [{}]".format(who_wrote.name, who_wrote.scroll)
|
||||||
|
|
||||||
# Two: What work did they do?
|
# Two: What work did they do?
|
||||||
|
|
@ -42,7 +41,7 @@ async def count_work_callback(slack: SlackClient, msg: dict, match: Match) -> No
|
||||||
# Three: check if we found anything
|
# Three: check if we found anything
|
||||||
if len(new_work) == 0:
|
if len(new_work) == 0:
|
||||||
if re.search(r'\s\d\s', text) is not None:
|
if re.search(r'\s\d\s', text) is not None:
|
||||||
slack_util.reply(slack, msg,
|
slack_util.get_slack().reply(event,
|
||||||
"If you were trying to record work, it was not recognized.\n"
|
"If you were trying to record work, it was not recognized.\n"
|
||||||
"Use words {} or work will not be recorded".format(counted_data))
|
"Use words {} or work will not be recorded".format(counted_data))
|
||||||
return
|
return
|
||||||
|
|
@ -59,7 +58,7 @@ async def count_work_callback(slack: SlackClient, msg: dict, match: Match) -> No
|
||||||
fmt_work_dict(new_work),
|
fmt_work_dict(new_work),
|
||||||
contribution_count,
|
contribution_count,
|
||||||
new_total))
|
new_total))
|
||||||
slack_util.reply(slack, msg, congrats)
|
slack_util.get_slack().reply(event, congrats)
|
||||||
|
|
||||||
|
|
||||||
async def record_towel_contribution(for_brother: Brother, contribution_count: int) -> int:
|
async def record_towel_contribution(for_brother: Brother, contribution_count: int) -> int:
|
||||||
|
|
@ -91,5 +90,5 @@ async def record_towel_contribution(for_brother: Brother, contribution_count: in
|
||||||
# Make dem HOOKs
|
# Make dem HOOKs
|
||||||
count_work_hook = slack_util.ChannelHook(count_work_callback,
|
count_work_hook = slack_util.ChannelHook(count_work_callback,
|
||||||
patterns=".*",
|
patterns=".*",
|
||||||
channel_whitelist=[channel_util.SLAVES_TO_THE_MACHINE_ID],
|
channel_whitelist=["#slavestothemachine"],
|
||||||
consumer=False)
|
consumer=False)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue