192bc206e75b860661816837d396afc3059ac6b1
[kiosk.git] / reddit_renderer.py
1 #!/usr/bin/env python3
2
3 import logging
4 from typing import Callable, Dict, Iterable, List, Set
5
6 import praw  # type: ignore
7
8 import file_writer
9 import grab_bag
10 import page_builder
11 import profanity_filter
12 import renderer
13 import kiosk_secrets as secrets
14
15
16 logger = logging.getLogger(__file__)
17
18
19 class reddit_renderer(renderer.abstaining_renderer):
20     """A renderer to pull text content from reddit."""
21
22     def __init__(
23         self,
24         name_to_timeout_dict: Dict[str, int],
25         subreddit_list: List[str],
26         *,
27         min_votes: int = 20,
28         font_size: int = 24,
29         additional_filters: Iterable[Callable[[str], bool]] = [],
30     ):
31         super().__init__(name_to_timeout_dict)
32         self.subreddit_list = subreddit_list
33         self.praw = praw.Reddit(
34             client_id=secrets.reddit_client_id,
35             client_secret=secrets.reddit_client_secret,
36             user_agent=secrets.reddit_user_agent,
37         )
38         self.min_votes = min_votes
39         self.font_size = font_size
40         self.messages = grab_bag.grab_bag()
41         self.filters: List[Callable[..., bool]] = [
42             profanity_filter.ProfanityFilter().contains_bad_word
43         ]
44         self.filters.extend(additional_filters)
45         self.deduper: Set[str] = set()
46
47     def periodic_render(self, key: str) -> bool:
48         logger.debug('called for "%s"' % key)
49         if key == "Scrape":
50             return self.scrape_reddit()
51         elif key == "Shuffle":
52             return self.shuffle_messages()
53         else:
54             raise Exception("Unexpected operation")
55
56     def append_message(self, messages: List[str]) -> None:
57         for msg in messages:
58             title = str(msg.title)
59             if title in self.deduper:
60                 continue
61             filtered = ""
62             for filt in self.filters:
63                 if filt(title) is True:
64                     filtered = filt.__name__
65                     break
66             if filtered != "":
67                 logger.info(
68                     f'Filter {filtered} struck down "{title}"'
69                 )
70                 continue
71             if msg.ups < self.min_votes:
72                 logger.debug(
73                     f'"{title}" doesn\'t have enough upvotes to be interesting'
74                 )
75                 continue
76
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
102     def scrape_reddit(self) -> bool:
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             logger.debug(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             logger.debug("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().__init__(
160             name_to_timeout_dict, ["todayilearned"], min_votes=100, 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().__init__(
167             name_to_timeout_dict, ["quotes"], min_votes=100, font_size=20
168         )
169
170
171 class showerthoughts_reddit_renderer(reddit_renderer):
172     @staticmethod
173     def dont_tell_me_about_gift_cards(msg: str) -> bool:
174         return "gift card" in msg
175
176     def __init__(self, name_to_timeout_dict: Dict[str, int]):
177         super().__init__(
178             name_to_timeout_dict,
179             ["showerthoughts"],
180             min_votes=150,
181             additional_filters=[
182                 showerthoughts_reddit_renderer.dont_tell_me_about_gift_cards
183             ],
184         )
185
186
187 class seattle_reddit_renderer(reddit_renderer):
188     def __init__(self, name_to_timeout_dict: Dict[str, int]):
189         super().__init__(
190             name_to_timeout_dict,
191             ["seattle", "seattleWA", "SeaWA", "bellevue", "kirkland", "CoronavirusWA"],
192             min_votes=50,
193         )
194
195
196 class lifeprotips_reddit_renderer(reddit_renderer):
197     def __init__(self, name_to_timeout_dict: Dict[str, int]):
198         super().__init__(
199             name_to_timeout_dict, ["lifeprotips"], min_votes=50
200         )
201
202
203 #x = reddit_renderer({"Test", 1234}, ["seattle","bellevue"], min_votes=50, font_size=24)
204 #x.periodic_render("Scrape")
205 #x.periodic_render("Shuffle")