1 from turbot.blocks import (
2 section_block, text_block, button_block, accessory_block
4 from turbot.channel import channel_url
5 from boto3.dynamodb.conditions import Key
8 def find_puzzle_for_url(turb, hunt_id, url):
9 """Given a hunt_id and URL, return the puzzle with that URL
11 Returns None if no puzzle with the given URL exists in the database,
12 otherwise a dictionary with all fields from the puzzle's row in
16 response = turb.table.query(
17 IndexName='url_index',
18 KeyConditionExpression=(
19 Key('hunt_id').eq(hunt_id) &
24 if response['Count'] == 0:
27 return response['Items'][0]
29 def puzzle_blocks(puzzle):
30 """Generate Slack blocks for a puzzle
32 The puzzle argument should be a dictionary as returned from the
33 database. The return value can be used in a Slack command
34 expecting blocks to provide all the details of a puzzle, (its
35 state, solution, links to channel and sheet, etc.).
39 status = puzzle['status']
40 solution = puzzle['solution']
41 channel_id = puzzle['channel_id']
42 url = puzzle.get('url', None)
43 sheet_url = puzzle.get('sheet_url', None)
44 state = puzzle.get('state', None)
48 if status == 'solved':
49 status_emoji = ":ballot_box_with_check:"
51 status_emoji = ":white_square:"
54 solution_str = "*`" + '`, `'.join(solution) + "`*"
58 links.append("<{}|Puzzle>".format(url))
60 links.append("<{}|Sheet>".format(sheet_url))
64 state_str = "\n{}".format(state)
66 puzzle_text = "{}{} <{}|{}> ({}){}".format(
67 status_emoji, solution_str,
68 channel_url(channel_id), name,
69 ', '.join(links), state_str
72 # Combining hunt ID and puzzle ID together here is safe because
73 # both IDs are restricted to not contain a hyphen, (see
74 # valid_id_re in interaction.py)
75 hunt_and_puzzle = "{}-{}".format(puzzle['hunt_id'], puzzle['puzzle_id'])
79 section_block(text_block(puzzle_text)),
80 button_block("✏", "edit_puzzle", hunt_and_puzzle)
84 def puzzle_matches_one(puzzle, pattern):
85 """Returns True if this puzzle matches the given string (regexp)
87 A match will be considered on any of puzzle title, round title,
88 puzzle URL, puzzle state, or solution string. The string can
89 include regular expression syntax. Matching is case insensitive.
92 p = re.compile('.*'+pattern+'.*', re.IGNORECASE)
94 if p.match(puzzle['name']):
97 if 'rounds' in puzzle:
98 for round in puzzle['rounds']:
103 if p.match(puzzle['url']):
106 if 'state' in puzzle:
107 if p.match(puzzle['state']):
110 if 'solution' in puzzle:
111 for solution in puzzle['solution']:
112 if p.match(solution):
117 def puzzle_matches_all(puzzle, patterns):
118 """Returns True if this puzzle matches all of the given list of patterns
120 A match will be considered on any of puzzle title, round title,
121 puzzle URL, puzzle state, or solution string. All patterns must
122 match the puzzle somewhere, (that is, there is an implicit logical
123 AND between patterns). Patterns can include regular expression
124 syntax. Matching is case insensitive.
127 for pattern in patterns:
128 if not puzzle_matches_one(puzzle, pattern):