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