]> git.cworth.org Git - turbot/commitdiff
Add an initial implementation of /help
authorCarl Worth <cworth@cworth.org>
Mon, 11 Jan 2021 20:21:29 +0000 (12:21 -0800)
committerCarl Worth <cworth@cworth.org>
Mon, 11 Jan 2021 20:43:11 +0000 (12:43 -0800)
So that Turbot can be self-documenting.

turbot/help.py [new file with mode: 0644]
turbot/interaction.py

diff --git a/turbot/help.py b/turbot/help.py
new file mode 100644 (file)
index 0000000..dbecfe3
--- /dev/null
@@ -0,0 +1,224 @@
+overview = """
+*Turbot help*
+Here is an overview of commands that Turbot makes available. You can
+actually get at all functionality by just typing `/hunt` and then
+clicking buttons that are provided in the result. But many operations
+will be much more efficient by using a special-purpose command, (such
+as `/solved SOLUTION`).
+
+Type `/help <command-name>` for more details on any command below.
+
+*Hunt creation and editing*
+    `/new [hunt]`: Create a new hunt (from any channel)
+    `/edit [hunt]`: Edit the current hunt (in a hunt or puzzle channel)
+
+*Puzzle creation and editing* (in a puzzle channel unless specified)
+    `/new [puzzle]`: Create a new puzzle (in a hunt or puzzle channel)
+    `/edit [puzzle]`: Edit the current puzzle
+    `/state <update>`: Capture the current state
+    `/solved <solution>`: Mark current puzzle solved
+    `/tag [+ADD_TAG|-REMOVE_TAG]`: Tag/untag current puzzle
+
+*Hunt overview and searching for puzzles*
+    `/hunt <search_terms>`: Search for puzzles (in a hunt/puzzle channel)
+    `/round <search_terms>`: Show puzzles in current round (puzzle channel)
+    `/puzzle`: Display state of current puzzle (puzzle channel)
+
+*Solving support* (from any channel)
+    `/rot [*|COUNT] <text_to_rotate>`: Perform a Caesar shift on text
+"""
+
+commands = {
+    "edit": {
+        "summary": "Edit the puzzle or hunt for the current channel",
+        "usage": "`/edit`, `/edit hunt`, `/edit puzzle`",
+        "details": ("This command will bring up a dialog window where you " +
+                    "can edit the current hunt or puzzle. With `/edit` " +
+                    "alone it will edit whatever is associated with the " +
+                    "current channel (hunt or puzzle). With `/edit hunt` " +
+                    "you can edit the hunt from a puzzle channel.")
+    },
+    "hunt": {
+        "summary": "Search for and display puzzles in the current hunt",
+        "usage": "`/hunt [unsolved|solved|all] [meta|plain] <search_terms>`",
+        "details": ("With `/hunt` alone, this command will display all " +
+                    "unsolved puzzles in the current hunt. All output from " +
+                    "`/hunt` is entirely private to you, so you do not need " +
+                    "to worry about cluttering up the channel's chat " +
+                    "traffic with this command." +
+                    "\n\n" +
+                    "The output for each puzzle will include all of the " +
+                    "information displayed by the `/puzzle` command so see " +
+                    "`/help puzzle` for details." +
+                    "\n\n"
+                    "An initial argument can be used to instead display " +
+                    "only `solved` puzzles or `all` puzzles (both unsolved " +
+                    "and solved). Additional arguments are search terms. " +
+                    "Only puzzles that match all provided terms will be " +
+                    " displayed. The terms can match the puzzle title, " +
+                    " round title, puzzle URL, puzzle type (`meta` or " +
+                    "`plain`), tags, or a puzzle's solution." +
+                    "\n\n" +
+                    "Use quotation marks in search terms to search for a " +
+                    "phrase such as `/hunt \"good times\"`." +
+                    "\n\n" +
+                    "Search terms can include regular expression syntax." +
+                    "\n\n" +
+                    "This command also supports `/hunt new` and " +
+                    "`/hunt edit` which are identical to `/new hunt` " +
+                    "and `/edit hunt` so see `/help new` or `/help edit`" +
+                    "for details.")
+    },
+    "new": {
+        "summary": "Create a new puzzle (or a new hunt)",
+        "usage": "`/new`, `/new puzzle`, `/new hunt`",
+        "details": ("With `/new` alone, this command will bring up a " +
+                    "dialog window where you will be prompted for " +
+                    "information to create a new puzzle. The only field " +
+                    "required for a new puzzle is the puzzle's name " +
+                    "but any additional information you have will be " +
+                    "useful to add (such as an external URL for the puzzle.)" +
+                    "\n\n" +
+                    "A puzzle can be marked as a meta-puzzle and can be " +
+                    "assigned to one or more rounds. If the round(s) for " +
+                    "this puzzle already exist, simply click on them in " +
+                    "the \"Round(s)\" field. If this is the first puzzle " +
+                    "for a new round, then type the name of the round in " +
+                    "the \"New round(s)\" field." +
+                    "\n\n" +
+                    "For the new puzzle, Turbot will create a Slack channel " +
+                    "and a shared spreadsheet for the puzzle. It will also " +
+                    "announce the new puzzle in the hunt's channel, where " +
+                    "members of the hunt can click to join the puzzle's " +
+                    "channel and then click through to the puzzle's sheet." +
+                    "\n\n" +
+                    "This command can also be issued as `/new hunt` from " +
+                    "any channel to create an entirely new hunt.")
+    },
+    "puzzle": {
+        "summary": "Display the status of the current puzzle",
+        "usage": "`/puzzle`, `/puzzle new`, `/puzzle edit",
+        "details": ("When you issue `/puzzle` alone in a puzzle channel, " +
+                    "Turbot will reply (privately to you) with all the " +
+                    "information it has about the current puzzle. For all " +
+                    "puzzles this includes the solved status (either an " +
+                    "unchecked or checked checkbox), the puzzle title, " +
+                    "links to the puzzle's external web page and sheet, " +
+                    "the rounds of the puzzle, and tags, and the puzzle's " +
+                    "state string." +
+                    "\n\n" +
+                    "If this is a meta-puzzle the output will also include " +
+                    "the names (and solutions where known) of all other " +
+                    "puzzles in the same round as the current puzzle." +
+                    "\n\n" +
+                    "This command also support `/puzzle new` and " +
+                    "`/puzzle edit` which are identical to `/new puzzle` " +
+                    "and `/edit puzzle` so see `/help new` and `/help edit` " +
+                    "for details.")
+    },
+    "round": {
+        "summary": "Display puzzles in the same round as the current puzzle",
+        "usage": "`/round [unsolved|solved|all] [meta|plain] <search_terms>`",
+        "details": ("The `/round` command can be issued from any puzzle " +
+                    "channel and will display (by default) all puzzles " +
+                    "belonging to the same round(s) as the current puzzle." +
+                    "\n\n"
+                    "To filter to only solved or unsolved puzzles, issue " +
+                    "`/round solved` or `/round unsolved` instead." +
+                    "\n\n" +
+                    "This command also supports all of the same options as " +
+                    "the `/hunt` command for searching/filtering which " +
+                    "puzzles to display. So see `/help hunt` for details.")
+    },
+    "rot": {
+        "summary": "Perform a Caesar shift rotation on some text",
+        "usage": "`/rot TEXT`, `/rot * TEXT`, `/rot COUNT TEXT`",
+        "details": ("This command performs a Caesar shift rotation on the " +
+                    "text you provide to the command. An initial numeric " +
+                    "argument indicates how many places through the " +
+                    "alphabet each character should be rotated." +
+                    "\n\n" +
+                    "If the numeric argument is omitted, or is the " +
+                    "character `*` this command will emit every possible " +
+                    "rotation of the provided text." +
+                    "\n\n"
+                    "Note: Unlike commands such as `/hunt` or `/puzzle` the " +
+                    "output from `/rot` will be presented to the current " +
+                    "channel rather than privately to you. The idea behind " +
+                    "this is that others working on the same puzzle might " +
+                    "benefit from this output as well." +
+                    "\n\n" +
+                    "If you would like to execute this command privately, " +
+                    "may issue it in a direct-message conversation with " +
+                    "Turbot.")
+    },
+    "state": {
+        "summary": "Updates the state of this puzzle",
+        "usage": "`/state <WHERE THINGS STAND>`",
+        "details": ("This command allows you to capture a high-level " +
+                    "description of the state of a puzzle. This is " +
+                    "particularly useful when you are switching away from " +
+                    "a puzzle to let the next solvers know the state of " +
+                    "things." +
+                    "\n\n" +
+                    "Turbot will present the state string in every view it " +
+                    "generates for the puzzle." +
+                    "\n\n" +
+                    "An example state string might be something like: " +
+                    "`/state Grid is filled, but we need help on extraction`" +
+                    " or similar.")
+    },
+    "solved": {
+        "summary": "Record the solution of a puzzle",
+        "usage": "`/solved SOLUTION HERE`",
+        "details": ("Once you've submitted a solution to the hunt's website " +
+                    "and it has been confirmed, issue the `/solved` command " +
+                    "so that Turbot knows the puzzle is solved. Turbot will " +
+                    "report to the hunt's channel that the puzzle has been " +
+                    "solved.")
+    },
+    "tag": {
+        "summary": "Add or remove a tag to a puzzle",
+        "usage": "`/tag NEW_TAG`",
+        "details": ("This command allows you to add brief descriptive tags " +
+                    "to a puzzle. This might include puzzle types, specific " +
+                    "domain knowledge, or metadata from the puzzle’s " +
+                    "presentation which might be used elsewhere." +
+                    "\n\n" +
+                    "Tags can only consist of letters, numbers, and the " +
+                    "underscore character `_`." +
+                    "\n\n" +
+                    "You can also add a tag by typing `/tag +TAG_TO_ADD` " +
+                    "and remove a tag with `/tag -TAG_TO_REMOVE`.")
+    }
+}
+
+def turbot_help(args):
+    """The top-level implementation of the Turbot help system
+
+    With empty args, gives a summary of available commands.
+
+    With any command name, gives detailed help on that command.
+    """
+
+    if not args:
+        return overview
+
+    # We only look at the first word here
+    command = args.split(" ", 1)[0]
+
+    # Ignore any slash prefix the user may have given
+    if command[0] == '/':
+        command = command[1:]
+
+    if command not in commands:
+        valid = list(commands.keys())
+        valid.sort()
+        return "Unknown command: {}. Valid commands are: {}".format(
+            command, ", ".join(valid))
+
+    dict = commands[command]
+    return ("*Help on `/{}`*".format(command) +
+            "\n`/{}`: {}".format(command, dict["summary"]) +
+            "\nUsage: {}".format(dict["usage"]) +
+            "\n\n{}".format(dict["details"]))
index 69d4f2505d52a1fd1905b3c36298bfc8b068c466..53f45a701a9fb9bae68d41316af01fe29f518a14 100644 (file)
@@ -17,6 +17,7 @@ from turbot.puzzle import (
     puzzle_copy
 )
 from turbot.round import round_quoted_puzzles_titles_answers
+from turbot.help import turbot_help
 import turbot.rot
 import turbot.sheets
 import turbot.slack
@@ -1326,3 +1327,29 @@ def round(turb, body, args):
     return lambda_ok
 
 commands["/round"] = round
+
+def help_command(turb, body, args):
+    """Implementation of the /help command
+
+    Displays help on how to use Turbot.
+    """
+
+    channel_id = body['channel_id'][0]
+    response_url = body['response_url'][0]
+
+    help_string = turbot_help(args)
+
+    # The "/help me" command is special in that it reports in the
+    # current channel, (where all other commands report privately to
+    # the invoking user).
+    if args == "me":
+        turb.slack_client.chat_postMessage(
+            channel=channel_id, text=help_string)
+    else:
+        requests.post(response_url,
+                      json = {"text": help_string},
+                      headers = {"Content-type": "application/json"})
+
+    return lambda_ok
+
+commands["/help"] = help_command