from turbot.hunt import (
find_hunt_for_hunt_id,
hunt_blocks,
- hunt_puzzles_for_hunt_id
+ hunt_puzzles_for_hunt_id,
+ hunt_update_topic
)
from turbot.puzzle import (
find_puzzle_for_url,
"New round(s) this puzzle belongs to " +
"(comma separated)",
optional=True),
+ input_block("Tag(s)", "tags",
+ "Tags for this puzzle (comma separated)",
+ initial_value=", ".join(puzzle.get("tags", [])),
+ optional=True),
input_block("State", "state",
"State of this puzzle (partial progress, next steps)",
initial_value=puzzle.get("state", None),
if rounds:
puzzle['rounds'] = rounds
new_rounds = state['new_rounds']['new_rounds']['value']
+ tags = state['tags']['tags']['value']
puzzle_state = state['state']['state']['value']
if puzzle_state:
puzzle['state'] = puzzle_state
}
)
+ # Process any tags
+ puzzle['tags'] = []
+ if tags:
+ for tag in tags.split(','):
+ # Drop any leading/trailing spaces from the tag
+ tag = tag.strip().upper()
+ # Ignore any empty string
+ if not len(tag):
+ continue
+ # Reject a tag that is not alphabetic or underscore A-Z_
+ if not re.match(r'^[A-Z0-9_]*$', tag):
+ return submission_error(
+ "tags",
+ "Error: Tags can only contain letters, numbers, "
+ + "and the underscore character."
+ )
+ puzzle['tags'].append(tag)
+
# Get old puzzle from the database (to determine what's changed)
old_puzzle = find_puzzle_for_sort_key(turb,
puzzle['hunt_id'],
puzzle['SK'])
# If we are changing puzzle type (meta -> plain or plain -> meta)
- # the the sort key has to change, so compute the new one and delete
+ # then the sort key has to change, so compute the new one and delete
# the old item from the database.
#
# XXX: We should really be using a transaction here to combine the
turb.slack_client, puzzle['channel_id'],
edit_message, blocks=blocks)
+ # Advertize any tag additions to the hunt
+ hunt = find_hunt_for_hunt_id(turb, puzzle['hunt_id'])
+
+ new_tags = set(puzzle['tags']) - set(old_puzzle['tags'])
+ if new_tags:
+ message = "Puzzle <{}|{}> has been tagged: {}".format(
+ puzzle['channel_url'],
+ puzzle['name'],
+ ", ".join(['`{}`'.format(t) for t in new_tags])
+ )
+ slack_send_message(turb.slack_client, hunt['channel_id'], message)
+
# Also inform the hunt if the puzzle's solved status changed
if puzzle['status'] != old_puzzle['status']:
- hunt = find_hunt_for_hunt_id(turb, puzzle['hunt_id'])
if puzzle['status'] == 'solved':
message = "Puzzle <{}|{}> has been solved!".format(
puzzle['channel_url'],
input_block("Hunt URL", "url", "External URL of hunt",
initial_value=hunt.get("url", None),
optional=True),
+ input_block("State", "state",
+ "State of the hunt (goals, upcoming meetings, etc.)",
+ initial_value=hunt.get("state", None),
+ optional=True),
checkbox_block("Is this hunt active?", "Active", "active",
checked=(hunt.get('active', False)))
]
if url:
hunt['url'] = url
+ hunt_state = state['state']['state']['value']
+ if hunt_state:
+ hunt['state'] = hunt_state
if state['active']['active']['selected_options']:
hunt['active'] = True
else:
turb.slack_client, hunt['channel_id'],
edit_message, blocks=blocks)
+ # Update channel topic and description
+ hunt_update_topic(turb, hunt)
+
return lambda_ok
def new_hunt_command(turb, body):
item['url'] = url
turb.table.put_item(Item=item)
+ # Update channel topic and description
+ hunt_update_topic(turb, item)
+
# Invite the initiating user to the channel
turb.slack_client.conversations_invite(channel=channel_id, users=user_id)
input_block("New round(s)", "new_rounds",
"New round(s) this puzzle belongs to " +
"(comma separated)",
- optional=True)
+ optional=True),
+ input_block("Tag(s)", "tags",
+ "Tags for this puzzle (comma separated)",
+ optional=True),
]
}
else:
rounds = []
new_rounds = state['new_rounds']['new_rounds']['value']
+ tags = state['tags']['tags']['value']
# Create a Slack-channel-safe puzzle_id
puzzle['puzzle_id'] = puzzle_id_from_name(puzzle['name'])
}
)
+ # Process any tags
+ puzzle['tags'] = []
+ if tags:
+ for tag in tags.split(','):
+ # Drop any leading/trailing spaces from the tag
+ tag = tag.strip().upper()
+ # Ignore any empty string
+ if not len(tag):
+ continue
+ # Reject a tag that is not alphabetic or underscore A-Z_
+ if not re.match(r'^[A-Z0-9_]*$', tag):
+ return submission_error(
+ "tags",
+ "Error: Tags can only contain letters, numbers, "
+ + "and the underscore character."
+ )
+ puzzle['tags'].append(tag)
+
if rounds:
puzzle['rounds'] = rounds
puzzle_update_channel_and_sheet(turb, puzzle, old_puzzle=old_puzzle)
+ # Advertize any tag additions to the hunt
+ new_tags = set(puzzle['tags']) - set(old_puzzle['tags'])
+ if new_tags:
+ hunt = find_hunt_for_hunt_id(turb, puzzle['hunt_id'])
+ message = "Puzzle <{}|{}> has been tagged: {}".format(
+ puzzle['channel_url'],
+ puzzle['name'],
+ ", ".join(['`{}`'.format(t) for t in new_tags])
+ )
+ slack_send_message(turb.slack_client, hunt['channel_id'], message)
+
return lambda_ok
commands["/tag"] = tag
# Report the solution to the puzzle's channel
slack_send_message(
turb.slack_client, channel_id,
- "Puzzle mark solved by <@{}>: `{}`".format(user_id, args))
+ "Puzzle marked solved by <@{}>: `{}`".format(user_id, args))
# Also report the solution to the hunt channel
hunt = find_hunt_for_hunt_id(turb, puzzle['hunt_id'])
commands["/solved"] = solved
+def delete(turb, body, args):
+ """Implementation of the /delete command
+
+ The argument to this command is the ID of a hunt.
+
+ The command will report an error if the specified hunt is active.
+
+ If the hunt is inactive, this command will archive all channels
+ from the hunt.
+ """
+
+ if not args:
+ return bot_reply("Error, no hunt provided. Usage: `/delete HUNT_ID`")
+
+ hunt_id = args
+ hunt = find_hunt_for_hunt_id(turb, hunt_id)
+
+ if not hunt:
+ return bot_reply("Error, no hunt named \"{}\" exists.".format(hunt_id))
+
+ if hunt['active']:
+ return bot_reply(
+ "Error, refusing to delete active hunt \"{}\".".format(hunt_id)
+ )
+
+ if hunt['hunt_id'] != hunt_id:
+ return bot_reply(
+ "Error, expected hunt ID of \"{}\" but found \"{}\".".format(
+ hunt_id, hunt['hunt_id']
+ )
+ )
+
+ puzzles = hunt_puzzles_for_hunt_id(turb, hunt_id)
+
+ for puzzle in puzzles:
+ channel_id = puzzle['channel_id']
+ turb.slack_client.conversations_archive(channel=channel_id)
+
+ turb.slack_client.conversations_archive(channel=hunt['channel_id'])
+
+ return lambda_ok
+
+commands["/delete"] = delete
+
def hunt(turb, body, args):
"""Implementation of the /hunt command