]> git.cworth.org Git - turbot/blobdiff - turbot/interaction.py
Implement a /round command
[turbot] / turbot / interaction.py
index e68aa6f276a7754e60d38a1c73cafbbc70825ec2..f9640ca3473b3abb8adc8e742b9065112c389158 100644 (file)
@@ -75,7 +75,25 @@ def multi_static_select(turb, payload):
 
 actions['multi_static_select'] = {"*": multi_static_select}
 
-def edit_puzzle(turb, payload):
+def edit_puzzle_command(turb, body):
+    """Implementation of the `/puzzle edit` command
+
+    As dispatched from the puzzle() function.
+    """
+
+    channel_id = body['channel_id'][0]
+    trigger_id = body['trigger_id'][0]
+
+    puzzle = puzzle_for_channel(turb, channel_id)
+
+    if not puzzle:
+        return bot_reply("Sorry, this does not appear to be a puzzle channel.")
+
+    return edit_puzzle(turb, puzzle, trigger_id)
+
+    return lambda_ok
+
+def edit_puzzle_button(turb, payload):
     """Handler for the action of user pressing an edit_puzzle button"""
 
     action_id = payload['actions'][0]['action_id']
@@ -92,7 +110,18 @@ def edit_puzzle(turb, payload):
                       headers = {"Content-type": "application/json"})
         return bot_reply("Error: Puzzle not found.")
 
-    round_options = hunt_rounds(turb, hunt_id)
+    return edit_puzzle(turb, puzzle, trigger_id)
+
+actions['button']['edit_puzzle'] = edit_puzzle_button
+
+def edit_puzzle(turb, puzzle, trigger_id):
+    """Common code for implementing an edit puzzle dialog
+
+    This implementation is common whether the edit operation was invoked
+    by a button (edit_puzzle_button) or a command (edit_puzzle_command).
+    """
+
+    round_options = hunt_rounds(turb, puzzle['hunt_id'])
 
     if len(round_options):
         round_options_block = [
@@ -116,9 +145,9 @@ def edit_puzzle(turb, payload):
     view = {
         "type": "modal",
         "private_metadata": json.dumps({
-            "hunt_id": hunt_id,
+            "hunt_id": puzzle['hunt_id'],
             "SK": puzzle["SK"],
-            "puzzle_id": puzzle_id,
+            "puzzle_id": puzzle['puzzle_id'],
             "channel_id": puzzle["channel_id"],
             "channel_url": puzzle["channel_url"],
             "sheet_url": puzzle["sheet_url"],
@@ -158,8 +187,6 @@ def edit_puzzle(turb, payload):
 
     return lambda_ok
 
-actions['button']['edit_puzzle'] = edit_puzzle
-
 def edit_puzzle_submission(turb, payload, metadata):
     """Handler for the user submitting the edit puzzle modal
 
@@ -247,6 +274,19 @@ def edit_puzzle_submission(turb, payload, metadata):
         turb.slack_client, puzzle['channel_id'],
         edit_message, blocks=blocks)
 
+    # 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'],
+                puzzle['name'])
+        else:
+            message = "Oops. Puzzle <{}|{}> has been marked unsolved!".format(
+                puzzle['channel_url'],
+                puzzle['name'])
+        slack_send_message(turb.slack_client, hunt['channel_id'], message)
+
     # We need to set the channel topic if any of puzzle name, url,
     # state, status, or solution, has changed. Let's just do that
     # unconditionally here.
@@ -560,6 +600,8 @@ def puzzle(turb, body, args):
 
         /puzzle new: Bring up a dialog to create a new puzzle
 
+        /puzzle edit: Edit the puzzle for the current channel
+
     Or with no argument at all:
 
         /puzzle: Print details of the current puzzle (if in a puzzle channel)
@@ -568,9 +610,14 @@ def puzzle(turb, body, args):
     if args == 'new':
         return new_puzzle(turb, body)
 
+    if args == 'edit':
+        return edit_puzzle_command(turb, body)
+
     if len(args):
         return bot_reply("Unknown syntax for `/puzzle` command. " +
-                         "Use `/puzzle new` to create a new puzzle.")
+                         "Valid commands are: `/puzzle`, `/puzzle edit`, " +
+                         "and `/puzzle new` to display, edit, or create " +
+                         "a puzzle.")
 
     # For no arguments we print the current puzzle as a reply
     channel_id = body['channel_id'][0]
@@ -811,15 +858,17 @@ def solved(turb, body, args):
 
 commands["/solved"] = solved
 
-
 def hunt(turb, body, args):
     """Implementation of the /hunt command
 
     The (optional) args string can be used to filter which puzzles to
     display. The first word can be one of 'all', 'unsolved', or
     'solved' and can be used to display only puzzles with the given
-    status. Any remaining text in the args string will be interpreted
-    as search terms. These will be split into separate terms on space
+    status. If this first word is missing, this command will display
+    only unsolved puzzles by default.
+
+    Any remaining text in the args string will be interpreted as
+    search terms. These will be split into separate terms on space
     characters, (though quotation marks can be used to include a space
     character in a term). All terms must match on a puzzle in order
     for that puzzle to be included. But a puzzle will be considered to
@@ -869,3 +918,77 @@ def hunt(turb, body, args):
     return lambda_ok
 
 commands["/hunt"] = hunt
+
+def round(turb, body, args):
+    """Implementation of the /round command
+
+    Displays puzzles in the same round(s) as the puzzle for the
+    current channel.
+
+    The (optional) args string can be used to filter which puzzles to
+    display. The first word can be one of 'all', 'unsolved', or
+    'solved' and can be used to display only puzzles with the given
+    status. If this first word is missing, this command will display
+    all puzzles in the round by default.
+
+    Any remaining text in the args string will be interpreted as
+    search terms. These will be split into separate terms on space
+    characters, (though quotation marks can be used to include a space
+    character in a term). All terms must match on a puzzle in order
+    for that puzzle to be included. But a puzzle will be considered to
+    match if any of the puzzle title, round title, puzzle URL, puzzle
+    state, or puzzle solution match. Matching will be performed
+    without regard to case sensitivity and the search terms can
+    include regular expression syntax.
+    """
+
+    channel_id = body['channel_id'][0]
+    response_url = body['response_url'][0]
+
+    puzzle = puzzle_for_channel(turb, channel_id)
+    hunt = hunt_for_channel(turb, channel_id)
+
+    if not puzzle:
+        if hunt:
+            return bot_reply(
+                "This is not a puzzle channel, but is a hunt channel. "
+                + "Use /hunt if you want to see all rounds for this hunt.")
+        else:
+            return bot_reply(
+                "Sorry, this channel doesn't appear to be a puzzle channel "
+                + "so the `/round` command cannot work here.")
+
+    terms = None
+    if args:
+        # The first word can be a puzzle status and all remaining word
+        # (if any) are search terms. _But_, if the first word is not a
+        # valid puzzle status ('all', 'unsolved', 'solved'), then all
+        # words are search terms and we default status to 'unsolved'.
+        split_args = args.split(' ', 1)
+        status = split_args[0]
+        if (len(split_args) > 1):
+            terms = split_args[1]
+        if status not in ('unsolved', 'solved', 'all'):
+            terms = args
+            status = 'all'
+    else:
+        status = 'all'
+
+    # Separate search terms on spaces (but allow for quotation marks
+    # to capture spaces in a search term)
+    if terms:
+        terms = shlex.split(terms)
+
+    blocks = hunt_blocks(turb, hunt,
+                         puzzle_status=status, search_terms=terms,
+                         limit_to_rounds=puzzle.get('rounds', [])
+                         )
+
+    requests.post(response_url,
+                  json = { 'blocks': blocks },
+                  headers = {'Content-type': 'application/json'}
+                  )
+
+    return lambda_ok
+
+commands["/round"] = round