]> git.cworth.org Git - turbot/blob - turbot/puzzle.py
Add state string to list of puzzle attributes matched in searching
[turbot] / turbot / puzzle.py
1 from turbot.blocks import section_block, text_block
2 from turbot.channel import channel_url
3 from boto3.dynamodb.conditions import Key
4 import re
5
6 def find_puzzle_for_url(turb, hunt_id, url):
7     """Given a hunt_id and URL, return the puzzle with that URL
8
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
11     the database.
12     """
13
14     response = turb.table.query(
15         IndexName='url_index',
16         KeyConditionExpression=(
17             Key('hunt_id').eq(hunt_id) &
18             Key('url').eq(url)
19         )
20     )
21
22     if response['Count'] == 0:
23         return None
24
25     return response['Items'][0]
26
27 def puzzle_block(puzzle):
28     """Generate Slack blocks for a puzzle
29
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.).
34     """
35
36     name = puzzle['name']
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)
43     status_emoji = ''
44     solution_str = ''
45
46     if status == 'solved':
47         status_emoji = ":ballot_box_with_check:"
48     else:
49         status_emoji = ":white_square:"
50
51     if len(solution):
52         solution_str = "*`" + '`, `'.join(solution) + "`*"
53
54     links = []
55     if url:
56         links.append("<{}|Puzzle>".format(url))
57     if sheet_url:
58         links.append("<{}|Sheet>".format(sheet_url))
59
60     state_str = ''
61     if state:
62         state_str = "\n{}".format(state)
63
64     puzzle_text = "{}{} <{}|{}> ({}){}".format(
65         status_emoji, solution_str,
66         channel_url(channel_id), name,
67         ', '.join(links), state_str
68     )
69
70     return section_block(text_block(puzzle_text))
71
72 def puzzle_matches_one(puzzle, pattern):
73     """Returns True if this puzzle matches the given string (regexp)
74
75     A match will be considered on any of puzzle title, round title,
76     puzzle URL, puzzle state, or solution string. The string can
77     include regular expression syntax. Matching is case insensitive.
78     """
79
80     p = re.compile('.*'+pattern+'.*', re.IGNORECASE)
81
82     if p.match(puzzle['name']):
83         return True
84
85     if 'rounds' in puzzle:
86         for round in puzzle['rounds']:
87             if p.match(round):
88                 return True
89
90     if 'url' in puzzle:
91         if p.match(puzzle['url']):
92             return True
93
94     if 'state' in puzzle:
95         if p.match(puzzle['state']):
96             return True
97
98     if 'solution' in puzzle:
99         for solution in puzzle['solution']:
100             if p.match(solution):
101                 return True
102
103     return False
104
105 def puzzle_matches_all(puzzle, patterns):
106     """Returns True if this puzzle matches all of the given list of patterns
107
108     A match will be considered on any of puzzle title, round title,
109     puzzle URL, puzzle state, or solution string. All patterns must
110     match the puzzle somewhere, (that is, there is an implicit logical
111     AND between patterns). Patterns can include regular expression
112     syntax. Matching is case insensitive.
113     """
114
115     for pattern in patterns:
116         if not puzzle_matches_one(puzzle, pattern):
117             return False
118
119     return True