e9c15142786d3f2a04edfbf707c092254340c8d2
[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 from pyutilz import profanity_filter
11
12 import file_writer
13 import grab_bag
14 import page_builder
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         self.pfilter = profanity_filter.ProfanityFilter()
27
28     def debug_prefix(self) -> str:
29         return "stranger"
30
31     def periodic_render(self, key: str) -> bool:
32         logger.debug("called for action %s" % key)
33         if key == "Fetch Events":
34             return self.fetch_events()
35         elif key == "Shuffle Events":
36             return self.shuffle_events()
37         else:
38             raise Exception("Unknown operaiton")
39
40     def get_style(self):
41         return """
42 <STYLE>
43 .calendar-post {
44   line-height: 96%;
45 }
46 .calendar-post-title a {
47   text-decoration: none;
48   color:black;
49   font-size:125%
50   line-height:104%;
51 }
52 .calendar-post-date {
53 }
54 .calendar-post-category {
55 }
56 .calendar-post-location {
57 }
58 .calendar-post-price {
59 }
60 .calendar-touch-link {
61 }
62 .calendar-category {
63   background-color:lightyellow;
64   color:black;
65   border:none;
66   font-size:50%;
67   padding:1px;
68 }
69 .calendar-post-price-mobile {
70   visibility: hidden;
71 }
72 .img-responsive {
73   float: left;
74   margin: 10px 10px 10px 10px;
75 }
76 </STYLE>"""
77
78     def shuffle_events(self) -> bool:
79         layout = page_builder.page_builder()
80         layout.set_layout(page_builder.page_builder.LAYOUT_FOUR_ITEMS)
81         layout.set_title("Stranger Events")
82         layout.set_style(self.get_style())
83         subset = self.events.subset(4)
84         if subset is None:
85             logger.debug("Not enough events to build page.")
86             return False
87
88         for msg in subset:
89             layout.add_item(msg)
90         with file_writer.file_writer("stranger-events_2_36000.html") as f:
91             layout.render_html(f)
92         return True
93
94     def fetch_events(self) -> bool:
95         self.events.clear()
96         feed_uris = []
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"/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"/seattle/events/?start-date={next_sat}&page=1")
108             feed_uris.append(f"/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"/seattle/events/?start-date={next_sun}&page=1")
114             feed_uris.append(f"/seattle/events/?start-date={next_sun}&page=2")
115
116         for uri in feed_uris:
117             try:
118                 logger.debug("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                     logger.debug("Connection failed, status %d" % (response.status))
124                     logger.debug(str(response.getheaders()))
125                     continue
126                 raw = response.read()
127             except Exception:
128                 logger.debug("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 self.pfilter.contains_bad_word(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             logger.debug(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")