]> git.cworth.org Git - turbot/blobdiff - turbot/interaction.py
Rename puzzle creation command from "/puzzle" to "/puzzle new"
[turbot] / turbot / interaction.py
index 8ae4a53556d45542ec19dca22471e46b89f8f702..bbaa51d1fd862e2e38183c1e1219d3e7ee321cdf 100644 (file)
@@ -3,7 +3,12 @@ from turbot.blocks import (
     input_block, section_block, text_block, multi_select_block, checkbox_block
 )
 from turbot.hunt import find_hunt_for_hunt_id, hunt_blocks
-from turbot.puzzle import find_puzzle_for_url, find_puzzle_for_puzzle_id
+from turbot.puzzle import (
+    find_puzzle_for_url,
+    find_puzzle_for_puzzle_id,
+    puzzle_update_channel_and_sheet,
+    puzzle_id_from_name
+)
 import turbot.rot
 import turbot.sheets
 import turbot.slack
@@ -197,6 +202,15 @@ def edit_puzzle_submission(turb, payload, metadata):
             sol.strip() for sol in solution.split(',')
         ]
 
+    # Verify that there's a solution if the puzzle is mark solved
+    if puzzle['status'] == 'solved' and not puzzle['solution']:
+        return submission_error("solution",
+                                "A solved puzzle requires a solution.")
+
+    if puzzle['status'] == 'unsolved' and puzzle['solution']:
+        return submission_error("solution",
+                                "An unsolved puzzle should have no solution.")
+
     # Add any new rounds to the database
     if new_rounds:
         if 'rounds' not in puzzle:
@@ -215,20 +229,18 @@ def edit_puzzle_submission(turb, payload, metadata):
                 }
             )
 
+    # Get old puzzle from the database (to determine what's changed)
+    old_puzzle = find_puzzle_for_puzzle_id(turb,
+                                           puzzle['hunt_id'],
+                                           puzzle['puzzle_id'])
+
     # Update the puzzle in the database
     turb.table.put_item(Item=puzzle)
 
     # 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.
-
-    # XXX: What we really want here is a single function that sets the
-    # channel name, the channel topic, and the sheet name. That single
-    # function should be called anywhere there is code changing any of
-    # these things. This function could then also accept an optional
-    # "old_puzzle" argument and avoid changing any of those things
-    # that are unnecessary.
-    set_channel_topic(turb, puzzle)
+    puzzle_update_channel_and_sheet(turb, puzzle, old_puzzle=old_puzzle)
 
     return lambda_ok
 
@@ -534,8 +546,24 @@ def hunt_rounds(turb, hunt_id):
 def puzzle(turb, body, args):
     """Implementation of the /puzzle command
 
-    The args string is currently ignored (this command will bring up
-    a modal dialog for user input instead)."""
+    The args string can be a sub-command:
+
+        /puzzle new: Bring up a dialog to create a new puzzle
+    """
+
+    if args == 'new':
+        return new_puzzle(turb, body)
+
+    return bot_reply("Unknown syntax for `/puzzle` command. " +
+                     "Use `/puzzle new` to create a new puzzle.")
+
+commands["/puzzle"] = puzzle
+
+def new_puzzle(turb, body):
+    """Implementation of the "/puzzle new" command
+
+    This brings up a dialog box for creating a new puzzle.
+    """
 
     channel_id = body['channel_id'][0]
     trigger_id = body['trigger_id'][0]
@@ -581,17 +609,16 @@ def puzzle(turb, body, args):
                                           view=view)
 
     if (result['ok']):
-        submission_handlers[result['view']['id']] = puzzle_submission
+        submission_handlers[result['view']['id']] = new_puzzle_submission
 
     return lambda_ok
 
-commands["/puzzle"] = puzzle
-
-def puzzle_submission(turb, payload, metadata):
+def new_puzzle_submission(turb, payload, metadata):
     """Handler for the user submitting the new puzzle modal
 
-    This is the modal view presented to the user by the puzzle function
-    above."""
+    This is the modal view presented to the user by the new_puzzle
+    function above.
+    """
 
     # First, read all the various data from the request
     meta = json.loads(metadata)
@@ -617,7 +644,7 @@ def puzzle_submission(turb, payload, metadata):
                 "Error: A puzzle with this URL already exists.")
 
     # Create a Slack-channel-safe puzzle_id
-    puzzle_id = re.sub(r'[^a-zA-Z0-9_]', '', name).lower()
+    puzzle_id = puzzle_id_from_name(name)
 
     # Create a channel for the puzzle
     hunt_dash_channel = "{}-{}".format(hunt_id, puzzle_id)
@@ -667,41 +694,6 @@ def puzzle_submission(turb, payload, metadata):
 
     return lambda_ok
 
-# XXX: This duplicates functionality eith events.py:set_channel_description
-def set_channel_topic(turb, puzzle):
-    channel_id = puzzle['channel_id']
-    name = puzzle['name']
-    url = puzzle.get('url', None)
-    sheet_url = puzzle.get('sheet_url', None)
-    state = puzzle.get('state', None)
-    status = puzzle['status']
-
-    description = ''
-
-    if status == 'solved':
-        description += "SOLVED: `{}` ".format('`, `'.join(puzzle['solution']))
-
-    description += name
-
-    links = []
-    if url:
-        links.append("<{}|Puzzle>".format(url))
-    if sheet_url:
-        links.append("<{}|Sheet>".format(sheet_url))
-
-    if len(links):
-        description += "({})".format(', '.join(links))
-
-    if state:
-        description += " {}".format(state)
-
-    # Slack only allows 250 characters for a topic
-    if len(description) > 250:
-        description = description[:247] + "..."
-
-    turb.slack_client.conversations_setTopic(channel=channel_id,
-                                             topic=description)
-
 def state(turb, body, args):
     """Implementation of the /state command
 
@@ -710,17 +702,20 @@ def state(turb, body, args):
 
     channel_id = body['channel_id'][0]
 
-    puzzle = puzzle_for_channel(turb, channel_id)
+    old_puzzle = puzzle_for_channel(turb, channel_id)
 
-    if not puzzle:
+    if not old_puzzle:
         return bot_reply(
             "Sorry, the /state command only works in a puzzle channel")
 
-    # Set the state field in the database
+    # Make a copy of the puzzle object
+    puzzle = old_puzzle.copy()
+
+    # Update the puzzle in the database
     puzzle['state'] = args
     turb.table.put_item(Item=puzzle)
 
-    set_channel_topic(turb, puzzle)
+    puzzle_update_channel_and_sheet(turb, puzzle, old_puzzle=old_puzzle)
 
     return lambda_ok
 
@@ -734,15 +729,18 @@ def solved(turb, body, args):
     channel_id = body['channel_id'][0]
     user_name = body['user_name'][0]
 
-    puzzle = puzzle_for_channel(turb, channel_id)
+    old_puzzle = puzzle_for_channel(turb, channel_id)
 
-    if not puzzle:
+    if not old_puzzle:
         return bot_reply("Sorry, this is not a puzzle channel.")
 
     if not args:
         return bot_reply(
             "Error, no solution provided. Usage: `/solved SOLUTION HERE`")
 
+    # Make a copy of the puzzle object
+    puzzle = old_puzzle.copy()
+
     # Set the status and solution fields in the database
     puzzle['status'] = 'solved'
     puzzle['solution'].append(args)
@@ -765,19 +763,7 @@ def solved(turb, body, args):
     )
 
     # And update the puzzle's description
-    set_channel_topic(turb, puzzle)
-
-    # And rename the sheet to suffix with "-SOLVED"
-    turbot.sheets.renameSheet(turb, puzzle['sheet_url'],
-                              puzzle['name'] + "-SOLVED")
-
-    # Finally, rename the Slack channel to add the suffix '-solved'
-    channel_name = "{}-{}-solved".format(
-        puzzle['hunt_id'],
-        puzzle['puzzle_id'])
-    turb.slack_client.conversations_rename(
-        channel=puzzle['channel_id'],
-        name=channel_name)
+    puzzle_update_channel_and_sheet(turb, puzzle, old_puzzle=old_puzzle)
 
     return lambda_ok