1 from turbot.blocks import section_block, text_block
2 from turbot.channel import channel_url
3 from boto3.dynamodb.conditions import Key
6 def find_puzzle_for_url(turb, hunt_id, url):
7 """Given a hunt_id and URL, return the puzzle with that URL
9 Returns None if no puzzle with the given URL exists in the database,
10 otherwise a dictionary with all fields from the puzzle's row in
14 response = turb.table.query(
15 IndexName='url_index',
16 KeyConditionExpression=(
17 Key('hunt_id').eq(hunt_id) &
22 if response['Count'] == 0:
25 return response['Items'][0]
27 def puzzle_block(puzzle):
28 """Generate Slack blocks for a puzzle
30 The puzzle argument should be a dictionary as returned from the
31 database. The return value can be used in a Slack command
32 expecting blocks to provide all the details of a puzzle, (its
33 state, solution, links to channel and sheet, etc.).
37 status = puzzle['status']
38 solution = puzzle['solution']
39 channel_id = puzzle['channel_id']
40 url = puzzle.get('url', None)
41 sheet_url = puzzle.get('sheet_url', None)
42 state = puzzle.get('state', None)
46 if status == 'solved':
47 status_emoji = ":ballot_box_with_check:"
49 status_emoji = ":white_square:"
52 solution_str = "*`" + '`, `'.join(solution) + "`*"
56 links.append("<{}|Puzzle>".format(url))
58 links.append("<{}|Sheet>".format(sheet_url))
62 state_str = "\n{}".format(state)
64 puzzle_text = "{}{} <{}|{}> ({}){}".format(
65 status_emoji, solution_str,
66 channel_url(channel_id), name,
67 ', '.join(links), state_str
70 return section_block(text_block(puzzle_text))
72 def puzzle_matches_one(puzzle, pattern):
73 """Returns True if this puzzle matches the given string (regexp)
75 A match will be considered on any of puzzle title, round title,
76 puzzle URL, or solution string. The string can include regular
77 expression syntax. Matching is case insensitive.
80 p = re.compile('.*'+pattern+'.*', re.IGNORECASE)
82 if p.match(puzzle['name']):
85 if 'rounds' in puzzle:
86 for round in puzzle['rounds']:
90 if p.match(puzzle['url']):
93 for solution in puzzle['solution']:
99 def puzzle_matches_all(puzzle, patterns):
100 """Returns True if this puzzle matches all of the given list of patterns
102 A match will be considered on any of puzzle title, round title,
103 puzzle URL, or solution string. All patterns must match the puzzle
104 somewhere, (that is, there is an implicit logical AND between
105 patterns). Patterns can include regular expression
106 syntax. Matching is case insensitive.
109 for pattern in patterns:
110 if not puzzle_matches_one(puzzle, pattern):