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
74 section_block(text_block(puzzle_text)),
75 button_block("✏", puzzle['puzzle_id'])
79 def puzzle_matches_one(puzzle, pattern):
80 """Returns True if this puzzle matches the given string (regexp)
82 A match will be considered on any of puzzle title, round title,
83 puzzle URL, puzzle state, or solution string. The string can
84 include regular expression syntax. Matching is case insensitive.
87 p = re.compile('.*'+pattern+'.*', re.IGNORECASE)
89 if p.match(puzzle['name']):
92 if 'rounds' in puzzle:
93 for round in puzzle['rounds']:
98 if p.match(puzzle['url']):
101 if 'state' in puzzle:
102 if p.match(puzzle['state']):
105 if 'solution' in puzzle:
106 for solution in puzzle['solution']:
107 if p.match(solution):
112 def puzzle_matches_all(puzzle, patterns):
113 """Returns True if this puzzle matches all of the given list of patterns
115 A match will be considered on any of puzzle title, round title,
116 puzzle URL, puzzle state, or solution string. All patterns must
117 match the puzzle somewhere, (that is, there is an implicit logical
118 AND between patterns). Patterns can include regular expression
119 syntax. Matching is case insensitive.
122 for pattern in patterns:
123 if not puzzle_matches_one(puzzle, pattern):