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