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