Changes needed to get the kiosk to run on the rpi.
[kiosk.git] / reddit_renderer.py
1 #!/usr/bin/env python3
2
3 import praw  # type: ignore
4 import random
5 from typing import Callable, Dict, Iterable, List, Set
6
7 import constants
8 import file_writer
9 import grab_bag
10 import page_builder
11 import profanity_filter
12 import renderer
13 import secrets
14
15
16 class reddit_renderer(renderer.debuggable_abstaining_renderer):
17     """A renderer to pull text content from reddit."""
18
19     def __init__(
20         self,
21         name_to_timeout_dict: Dict[str, int],
22         subreddit_list: List[str],
23         *,
24         min_votes: int = 20,
25         font_size: int = 24,
26         additional_filters: Iterable[Callable[[str], bool]] = [],
27     ):
28         super(reddit_renderer, self).__init__(name_to_timeout_dict, True)
29         self.subreddit_list = subreddit_list
30         self.praw = praw.Reddit(
31             client_id=secrets.reddit_client_id,
32             client_secret=secrets.reddit_client_secret,
33             user_agent=secrets.reddit_user_agent,
34         )
35         self.min_votes = min_votes
36         self.font_size = font_size
37         self.messages = grab_bag.grab_bag()
38         self.filters: List[Callable[..., bool]] = [
39             profanity_filter.profanity_filter().contains_bad_words
40         ]
41         self.filters.extend(additional_filters)
42         self.deduper: Set[str] = set()
43
44     def debug_prefix(self) -> str:
45         x = ""
46         for subreddit in self.subreddit_list:
47             x += f"{subreddit} "
48         return f"reddit({x.strip()})"
49
50     def periodic_render(self, key: str) -> bool:
51         self.debug_print('called for "%s"' % key)
52         if key == "Scrape":
53             return self.scrape_reddit()
54         elif key == "Shuffle":
55             return self.shuffle_messages()
56         else:
57             raise Exception("Unexpected operation")
58
59     def append_message(self, messages: List[str]) -> None:
60         for msg in messages:
61             title = str(msg.title)
62             if title in self.deduper:
63                 continue
64             filtered = ""
65             for filt in self.filters:
66                 if filt(title) is True:
67                     filtered = filt.__name__
68                     break
69             if filtered != "":
70                 print(f'Filter {filtered} struck down "{title}"')
71                 continue
72             if msg.ups < self.min_votes:
73                 print(f'"{title}" doesn\'t have enough upvotes to be interesting')
74                 continue
75
76             try:
77                 self.deduper.add(title)
78                 content = f"{msg.ups}"
79                 if (
80                     msg.thumbnail != "self"
81                     and msg.thumbnail != "default"
82                     and msg.thumbnail != ""
83                 ):
84                     content = f'<IMG SRC="{msg.thumbnail}">'
85                 self.messages.add(
86                     f"""
87 <TABLE STYLE="font-size:{self.font_size}pt;">
88   <TR>
89     <!-- The number of upvotes or item image: -->
90     <TD STYLE="font-weight:900; padding:8px;">
91       <FONT COLOR="maroon" SIZE=40>{content}</FONT>
92     </TD>
93
94     <!-- The content and author: -->
95     <TD>
96       <B>{title}</B><BR><FONT COLOR=#bbbbbb>({msg.author})</FONT>
97     </TD>
98   </TR>
99 </TABLE>"""
100                 )
101             except:
102                 self.debug_print("Unexpected exception, skipping message.")
103
104     def scrape_reddit(self) -> bool:
105         self.deduper.clear()
106         self.messages.clear()
107         for subreddit in self.subreddit_list:
108             try:
109                 msg = self.praw.subreddit(subreddit).hot()
110                 self.append_message(msg)
111             except:
112                 pass
113             try:
114                 msg = self.praw.subreddit(subreddit).new()
115                 self.append_message(msg)
116             except:
117                 pass
118             try:
119                 msg = self.praw.subreddit(subreddit).rising()
120                 self.append_message(msg)
121             except:
122                 pass
123             try:
124                 msg = self.praw.subreddit(subreddit).controversial("week")
125                 self.append_message(msg)
126             except:
127                 pass
128             try:
129                 msg = self.praw.subreddit(subreddit).top("day")
130                 self.append_message(msg)
131             except:
132                 pass
133             self.debug_print(f"There are now {self.messages.size()} messages")
134         return True
135
136     def shuffle_messages(self) -> bool:
137         layout = page_builder.page_builder()
138         layout.set_layout(page_builder.page_builder.LAYOUT_FOUR_ITEMS)
139         x = ""
140         for subreddit in self.subreddit_list:
141             x += f"{subreddit} "
142         if len(x) > 30:
143             if "SeaWA" in x:
144                 x = "[local interests]"
145             else:
146                 x = "Unknown, fixme"
147         layout.set_title("Reddit /r/%s" % x.strip())
148         subset = self.messages.subset(4)
149         if subset is None:
150             self.debug_print("Not enough messages to pick from.")
151             return False
152         for msg in subset:
153             layout.add_item(msg)
154         with file_writer.file_writer("%s_4_10800.html" % self.subreddit_list[0]) as f:
155             layout.render_html(f)
156         return True
157
158
159 class til_reddit_renderer(reddit_renderer):
160     def __init__(self, name_to_timeout_dict: Dict[str, int]):
161         super(til_reddit_renderer, self).__init__(
162             name_to_timeout_dict, ["todayilearned"], min_votes=200, font_size=20
163         )
164
165
166 class quotes_reddit_renderer(reddit_renderer):
167     def __init__(self, name_to_timeout_dict: Dict[str, int]):
168         super(quotes_reddit_renderer, self).__init__(
169             name_to_timeout_dict, ["quotes"], min_votes=200, font_size=20
170         )
171
172
173 class showerthoughts_reddit_renderer(reddit_renderer):
174     @staticmethod
175     def dont_tell_me_about_gift_cards(msg: str) -> bool:
176         return not "IMPORTANT PSA: No, you did not win a gift card" in msg
177
178     def __init__(self, name_to_timeout_dict: Dict[str, int]):
179         super(showerthoughts_reddit_renderer, self).__init__(
180             name_to_timeout_dict,
181             ["showerthoughts"],
182             min_votes=250,
183             additional_filters=[
184                 showerthoughts_reddit_renderer.dont_tell_me_about_gift_cards
185             ],
186         )
187
188
189 class seattle_reddit_renderer(reddit_renderer):
190     def __init__(self, name_to_timeout_dict: Dict[str, int]):
191         super(seattle_reddit_renderer, self).__init__(
192             name_to_timeout_dict,
193             ["seattle", "seattleWA", "SeaWA", "bellevue", "kirkland", "CoronavirusWA"],
194             min_votes=50,
195         )
196
197
198 class lifeprotips_reddit_renderer(reddit_renderer):
199     def __init__(self, name_to_timeout_dict: Dict[str, int]):
200         super(lifeprotips_reddit_renderer, self).__init__(
201             name_to_timeout_dict, ["lifeprotips"], min_votes=100
202         )
203
204
205 #x = reddit_renderer({"Test", 1234}, ["seattle","bellevue"], min_votes=50, font_size=24)
206 #x.periodic_render("Scrape")
207 #x.periodic_render("Shuffle")