More cleanup.
[kiosk.git] / stranger_renderer.py
1 #!/usr/bin/env python3
2
3 import datetime
4 import http.client
5 import logging
6 import re
7 from typing import Dict
8
9 from bs4 import BeautifulSoup  # type: ignore
10
11 import file_writer
12 import grab_bag
13 import page_builder
14 import profanity_filter
15 import renderer
16
17
18 logger = logging.getLogger(__file__)
19
20
21 class stranger_events_renderer(renderer.abstaining_renderer):
22     def __init__(self, name_to_timeout_dict: Dict[str, int]):
23         super().__init__(name_to_timeout_dict)
24         self.feed_site = "everout.com"
25         self.events = grab_bag.grab_bag()
26
27     def debug_prefix(self) -> str:
28         return "stranger"
29
30     def periodic_render(self, key: str) -> bool:
31         logger.debug("called for action %s" % key)
32         if key == "Fetch Events":
33             return self.fetch_events()
34         elif key == "Shuffle Events":
35             return self.shuffle_events()
36         else:
37             raise Exception("Unknown operaiton")
38
39     def get_style(self):
40         return """
41 <STYLE>
42 .calendar-post {
43   line-height: 96%;
44 }
45 .calendar-post-title a {
46   text-decoration: none;
47   color:black;
48   font-size:125%
49   line-height:104%;
50 }
51 .calendar-post-date {
52 }
53 .calendar-post-category {
54 }
55 .calendar-post-location {
56 }
57 .calendar-post-price {
58 }
59 .calendar-touch-link {
60 }
61 .calendar-category {
62   background-color:lightyellow;
63   color:black;
64   border:none;
65   font-size:50%;
66   padding:1px;
67 }
68 .calendar-post-price-mobile {
69   visibility: hidden;
70 }
71 .img-responsive {
72   float: left;
73   margin: 10px 10px 10px 10px;
74 }
75 </STYLE>"""
76
77     def shuffle_events(self) -> bool:
78         layout = page_builder.page_builder()
79         layout.set_layout(page_builder.page_builder.LAYOUT_FOUR_ITEMS)
80         layout.set_title("Stranger Events")
81         layout.set_style(self.get_style())
82         subset = self.events.subset(4)
83         if subset is None:
84             logger.debug("Not enough events to build page.")
85             return False
86
87         for msg in subset:
88             layout.add_item(msg)
89         with file_writer.file_writer("stranger-events_2_36000.html") as f:
90             layout.render_html(f)
91         return True
92
93     def fetch_events(self) -> bool:
94         self.events.clear()
95         feed_uris = [
96             "/seattle/events/?page=1",
97             "/seattle/events/?page=2",
98             "/seattle/events/?page=3",
99         ]
100         now = datetime.datetime.now()
101         ts = now + datetime.timedelta(1)
102         tomorrow = datetime.datetime.strftime(ts, "%Y-%m-%d")
103         feed_uris.append(f"/seattle/events/?start-date={tomorrow}")
104         delta = 5 - now.weekday()
105         if delta <= 0:
106             delta += 7
107         if delta > 1:
108             ts = now + datetime.timedelta(delta)
109             next_sat = datetime.datetime.strftime(ts, "%Y-%m-%d")
110             feed_uris.append(f"/seattle/events/?start-date={next_sat}&page=1")
111             feed_uris.append(f"/seattle/events/?start-date={next_sat}&page=2")
112         delta += 1
113         if delta > 1:
114             ts = now + datetime.timedelta(delta)
115             next_sun = datetime.datetime.strftime(ts, "%Y-%m-%d")
116             feed_uris.append(f"/seattle/events/?start-date={next_sun}&page=1")
117             feed_uris.append(f"/seattle/events/?start-date={next_sun}&page=2")
118
119         filter = profanity_filter.ProfanityFilter()
120         for uri in feed_uris:
121             try:
122                 logger.debug("fetching 'https://%s%s'" % (self.feed_site, uri))
123                 self.conn = http.client.HTTPSConnection(self.feed_site)
124                 self.conn.request("GET", uri, None, {"Accept-Charset": "utf-8"})
125                 response = self.conn.getresponse()
126                 if response.status != 200:
127                     logger.debug("Connection failed, status %d" % (response.status))
128                     logger.debug(str(response.getheaders()))
129                     continue
130                 raw = response.read()
131             except Exception:
132                 logger.debug("Exception talking to the stranger, ignoring.")
133                 continue
134
135             soup = BeautifulSoup(raw, "html.parser")
136             for x in soup.find_all("div", class_="row event list-item mb-3 py-3"):
137                 text = x.get_text()
138                 if filter.contains_bad_word(text):
139                     continue
140                 raw_str = str(x)
141                 raw_str = raw_str.replace(
142                     'src="/', 'align="left" src="https://www.thestranger.com/'
143                 )
144                 raw_str = raw_str.replace('href="/', 'href="https://www.thestranger.com/')
145                 raw_str = raw_str.replace("FREE", "Free")
146                 raw_str = raw_str.replace("Save Event", "")
147                 raw_str = re.sub("^\s*$", "", raw_str, 0, re.MULTILINE)
148                 raw_str = re.sub(
149                     '<span[^<>]*class="calendar-post-ticket"[^<>]*>.*</#span>',
150                     "",
151                     raw_str,
152                     0,
153                     re.DOTALL | re.IGNORECASE,
154                 )
155                 self.events.add(raw_str)
156             logger.debug(f"fetched {self.events.size()} events so far.")
157         return self.events.size() > 0
158
159
160 # Test
161 #x = stranger_events_renderer({"Test", 123})
162 #x.periodic_render("Fetch Events")
163 #x.periodic_render("Shuffle Events")