All you fuckers.
[kiosk.git] / reddit_renderer.py
index 05b641d30f806f8850ea8fc62e8527d4c6ec93be..d24d36063d0ce16564c0e8d7a22ed3c4d8253fce 100644 (file)
-import constants
+#!/usr/bin/env python3
+
+import logging
+from typing import Callable, Dict, Iterable, List, Set
+
+import praw  # type: ignore
+
+from scottutilz import profanity_filter
+
 import file_writer
 import grab_bag
-import renderer
-import secrets
 import page_builder
-import praw
-import profanity_filter
-import random
+import renderer
+import kiosk_secrets as secrets
 
-class reddit_renderer(renderer.debuggable_abstaining_renderer):
+
+logger = logging.getLogger(__name__)
+
+
+class reddit_renderer(renderer.abstaining_renderer):
     """A renderer to pull text content from reddit."""
 
-    def __init__(self, name_to_timeout_dict, subreddit_list, min_votes, font_size):
-        super(reddit_renderer, self).__init__(name_to_timeout_dict, True)
+    def __init__(
+        self,
+        name_to_timeout_dict: Dict[str, int],
+        subreddit_list: List[str],
+        *,
+        min_votes: int = 20,
+        font_size: int = 24,
+        additional_filters: Iterable[Callable[[str], bool]] = [],
+    ):
+        super().__init__(name_to_timeout_dict)
         self.subreddit_list = subreddit_list
-        self.praw = praw.Reddit(client_id=secrets.reddit_client_id,
-                                client_secret=secrets.reddit_client_secret,
-                                user_agent="Yoshiatsu's Kitchen Kiosk by u/yoshiatsu ver 0.1, See http://wannabe.guru.org/svn/kiosk/trunk/reddit_renderer.py")
+        self.praw = praw.Reddit(
+            client_id=secrets.reddit_client_id,
+            client_secret=secrets.reddit_client_secret,
+            user_agent=secrets.reddit_user_agent,
+        )
         self.min_votes = min_votes
         self.font_size = font_size
         self.messages = grab_bag.grab_bag()
-        self.filter = profanity_filter.profanity_filter()
-        self.deduper = set()
+        self.filters: List[Callable[..., bool]] = [
+            profanity_filter.ProfanityFilter().contains_bad_word
+        ]
+        self.filters.extend(additional_filters)
+        self.deduper: Set[str] = set()
 
-    def debug_prefix(self):
-        x = ""
-        for subreddit in self.subreddit_list:
-            x += ("%s " % subreddit)
-        return "reddit(%s)" % x.strip()
-
-    def periodic_render(self, key):
-        self.debug_print('called for "%s"' % key)
+    def periodic_render(self, key: str) -> bool:
+        logger.debug('called for "%s"' % key)
         if key == "Scrape":
             return self.scrape_reddit()
         elif key == "Shuffle":
             return self.shuffle_messages()
         else:
-            raise error('Unexpected operation')
+            raise Exception("Unexpected operation")
 
-    def append_message(self, messages):
+    def append_message(self, messages: List[str]) -> None:
         for msg in messages:
-            if (not self.filter.contains_bad_words(msg.title)
-                and msg.ups > self.min_votes
-                and not msg.title in self.deduper):
-                try:
-                    self.deduper.add(msg.title)
-                    content = "%d" % msg.ups
-                    if (msg.thumbnail != "self" and
-                        msg.thumbnail != "default" and
-                        msg.thumbnail != ""):
-                        content = '<IMG SRC="%s">' % msg.thumbnail
-                    x = u"""
-<TABLE STYLE="font-size:%dpt;">
+            title = str(msg.title)
+            if title in self.deduper:
+                continue
+            filtered = ""
+            for filt in self.filters:
+                if filt(title) is True:
+                    filtered = filt.__name__
+                    break
+            if filtered != "":
+                logger.info(f'Filter {filtered} struck down "{title}"')
+                continue
+            if msg.ups < self.min_votes:
+                logger.debug(
+                    f'"{title}" doesn\'t have enough upvotes to be interesting'
+                )
+                continue
+
+            self.deduper.add(title)
+            content = f"{msg.ups}"
+            if (
+                msg.thumbnail != "self"
+                and msg.thumbnail != "default"
+                and msg.thumbnail != ""
+            ):
+                content = f'<IMG SRC="{msg.thumbnail}">'
+            self.messages.add(
+                f"""
+<TABLE STYLE="font-size:{self.font_size}pt;">
   <TR>
     <!-- The number of upvotes or item image: -->
     <TD STYLE="font-weight:900; padding:8px;">
-      <FONT COLOR="maroon" SIZE=40>%s</FONT>
+      <FONT COLOR="maroon" SIZE=40>{content}</FONT>
     </TD>
 
     <!-- The content and author: -->
     <TD>
-      <B>%s</B><BR><FONT COLOR=#bbbbbb>(%s)</FONT>
+      <B>{title}</B><BR><FONT COLOR=#bbbbbb>({msg.author})</FONT>
     </TD>
   </TR>
-</TABLE>""" % (self.font_size, content, msg.title, msg.author)
-                    self.messages.add(x.encode('utf8'))
-                except:
-                    self.debug_print('Unexpected exception, skipping message.')
-            else:
-                self.debug_print('skipped message "%s" for profanity or low score' % (
-                    msg.title.encode('utf8')))
+</TABLE>"""
+            )
 
-    def scrape_reddit(self):
+    def scrape_reddit(self) -> bool:
         self.deduper.clear()
         self.messages.clear()
         for subreddit in self.subreddit_list:
@@ -91,24 +118,24 @@ class reddit_renderer(renderer.debuggable_abstaining_renderer):
             except:
                 pass
             try:
-                msg = self.praw.subreddit(subreddit).controversial('week')
+                msg = self.praw.subreddit(subreddit).controversial("week")
                 self.append_message(msg)
             except:
                 pass
             try:
-                msg = self.praw.subreddit(subreddit).top('day')
+                msg = self.praw.subreddit(subreddit).top("day")
                 self.append_message(msg)
             except:
                 pass
-            self.debug_print("There are now %d messages" % self.messages.size())
+            logger.debug(f"There are now {self.messages.size()} messages")
         return True
 
-    def shuffle_messages(self):
+    def shuffle_messages(self) -> bool:
         layout = page_builder.page_builder()
         layout.set_layout(page_builder.page_builder.LAYOUT_FOUR_ITEMS)
         x = ""
         for subreddit in self.subreddit_list:
-            x += ("%s " % subreddit)
+            x += f"{subreddit} "
         if len(x) > 30:
             if "SeaWA" in x:
                 x = "[local interests]"
@@ -117,40 +144,57 @@ class reddit_renderer(renderer.debuggable_abstaining_renderer):
         layout.set_title("Reddit /r/%s" % x.strip())
         subset = self.messages.subset(4)
         if subset is None:
-            self.debug_print("Not enough messages to pick from.")
+            logger.debug("Not enough messages to pick from.")
             return False
         for msg in subset:
             layout.add_item(msg)
-        f = file_writer.file_writer("%s_4_10800.html" % self.subreddit_list[0])
-        layout.render_html(f)
-        f.close()
+        with file_writer.file_writer("%s_4_10800.html" % self.subreddit_list[0]) as f:
+            layout.render_html(f)
         return True
 
+
 class til_reddit_renderer(reddit_renderer):
-    def __init__(self, name_to_timeout_dict):
-        super(til_reddit_renderer, self).__init__(
-            name_to_timeout_dict, ["todayilearned"], 200, 20)
+    def __init__(self, name_to_timeout_dict: Dict[str, int]):
+        super().__init__(
+            name_to_timeout_dict, ["todayilearned"], min_votes=100, font_size=20
+        )
+
 
 class quotes_reddit_renderer(reddit_renderer):
-    def __init__(self, name_to_timeout_dict):
-        super(quotes_reddit_renderer, self).__init__(
-            name_to_timeout_dict, ["quotes"], 200, 20)
+    def __init__(self, name_to_timeout_dict: Dict[str, int]):
+        super().__init__(name_to_timeout_dict, ["quotes"], min_votes=100, font_size=20)
+
 
 class showerthoughts_reddit_renderer(reddit_renderer):
-    def __init__(self, name_to_timeout_dict):
-        super(showerthoughts_reddit_renderer, self).__init__(
-            name_to_timeout_dict, ["showerthoughts"], 350, 24)
+    @staticmethod
+    def dont_tell_me_about_gift_cards(msg: str) -> bool:
+        return "gift card" in msg
+
+    def __init__(self, name_to_timeout_dict: Dict[str, int]):
+        super().__init__(
+            name_to_timeout_dict,
+            ["showerthoughts"],
+            min_votes=150,
+            additional_filters=[
+                showerthoughts_reddit_renderer.dont_tell_me_about_gift_cards
+            ],
+        )
+
 
 class seattle_reddit_renderer(reddit_renderer):
-    def __init__(self, name_to_timeout_dict):
-        super(seattle_reddit_renderer, self).__init__(
-            name_to_timeout_dict, ["seattle","seattleWA","SeaWA","bellevue","kirkland", "CoronavirusWA"], 50, 24)
+    def __init__(self, name_to_timeout_dict: Dict[str, int]):
+        super().__init__(
+            name_to_timeout_dict,
+            ["seattle", "seattleWA", "SeaWA", "bellevue", "kirkland", "CoronavirusWA"],
+            min_votes=50,
+        )
+
 
 class lifeprotips_reddit_renderer(reddit_renderer):
-    def __init__(self, name_to_timeout_dict):
-        super(lifeprotips_reddit_renderer, self).__init__(
-            name_to_timeout_dict, ["lifeprotips"], 100, 24)
+    def __init__(self, name_to_timeout_dict: Dict[str, int]):
+        super().__init__(name_to_timeout_dict, ["lifeprotips"], min_votes=50)
+
 
-#x = reddit_renderer({"Test", 1234}, ["seattle","bellevue"], 50, 24)
-#x.periodic_render("Scrape")
-#x.periodic_render("Shuffle")
+# x = reddit_renderer({"Test", 1234}, ["seattle","bellevue"], min_votes=50, font_size=24)
+# x.periodic_render("Scrape")
+# x.periodic_render("Shuffle")