Skip to content

Commit

Permalink
Trigger daily thread rotation from new post feed rather than schedule…
Browse files Browse the repository at this point in the history
…d job
  • Loading branch information
durinthal committed Mar 13, 2024
1 parent ab7eaa8 commit 9819e3c
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 12 deletions.
10 changes: 10 additions & 0 deletions src/constants/post_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
Static values about posts that generally shouldn't need to change.
"""

LINK_REGEX = r"\[{name}\]\(.*?\)"
LINK_FORMAT = "[{name}]({link})"

DAILY_THREAD_NAME = "Anime Questions, Recommendations, and Discussion"
DAILY_THREAD_SHORT_NAME = "Daily Megathread"
DAILY_THREAD_AUTHOR = "AnimeMod"
11 changes: 11 additions & 0 deletions src/feeds/new_posts.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from praw.models.reddit.submission import Submission

import config_loader
from constants import post_constants
from services import post_service, base_data_service
from utils import discord, reddit as reddit_utils
from utils.logger import logger
Expand Down Expand Up @@ -45,6 +46,16 @@ def process_post(submission: Submission):

base_data_service.update(post)

# If this is a new daily thread, do all the updating for it.
if (
author_name == post_constants.DAILY_THREAD_AUTHOR
and post_constants.DAILY_THREAD_NAME.lower() in post.title.lower()
):
try:
post_service.rotate_daily_thread(post)
except Exception:
logger.exception("Unable to process new daily thread")

logger.debug(f"Finished processing {post.id36}")


Expand Down
22 changes: 13 additions & 9 deletions src/old/menuupdater.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import praw

import config_loader
from constants import post_constants
from services import post_service, sidebar_service
from utils import reddit
from utils.logger import logger


SEARCH_TIMEOUT = 3600
LINK_REGEX = r"\[{name}\]\(.*?\)"
LINK_FORMAT = "[{name}]({link})"


class SubredditMenuUpdater:
Expand Down Expand Up @@ -39,8 +39,12 @@ def __init__(self, name, short_name, author, debug=False):
self.subreddit = self.reddit.subreddit(config_loader.REDDIT["subreddit"])

post = self._find_post(name, author)
self._update_menus(name, post)
self._update_redesign_menus(name, short_name, post)
db_post = post_service.get_post_by_id(post.id)
sidebar_service.replace_sidebar_link(name, reddit.make_relative_link(db_post), self.subreddit)
sidebar_service.update_redesign_menus(name, short_name, db_post, self.subreddit)

# self._update_menus(name, post)
# self._update_redesign_menus(name, short_name, post)
logger.info(f"Completed running subreddit menu updater for {name}")

def _find_post(self, name, author):
Expand Down Expand Up @@ -73,8 +77,8 @@ def _update_menus(self, name, post):
"""
logger.debug("Updating menus on old Reddit")

pattern_match = LINK_REGEX.format(name=name)
pattern_replace = LINK_FORMAT.format(name=name, link=post.shortlink)
pattern_match = post_constants.LINK_REGEX.format(name=name)
pattern_replace = post_constants.LINK_FORMAT.format(name=name, link=post.shortlink)

sidebar = self.subreddit.wiki["config/sidebar"]
sidebar_text = sidebar.content_md
Expand Down Expand Up @@ -111,8 +115,8 @@ def _update_redesign_menus(self, name, short_name, post):
topmenu.mod.update(data=list(topmenu))
logger.debug("Topbar menu updated")

pattern_match = LINK_REGEX.format(name=name)
pattern_replace = LINK_FORMAT.format(name=name, link=post.shortlink)
pattern_match = post_constants.LINK_REGEX.format(name=name)
pattern_replace = post_constants.LINK_FORMAT.format(name=name, link=post.shortlink)

sidemenu = self._get_redesign_sidemenu(name)
sidemenu_text = sidemenu.text
Expand Down Expand Up @@ -164,7 +168,7 @@ def _get_redesign_sidemenu(self, name):
"""
sidebar = self.subreddit.widgets.sidebar

pattern_match = LINK_REGEX.format(name=name)
pattern_match = post_constants.LINK_REGEX.format(name=name)

for widget in sidebar:
if isinstance(widget, praw.models.TextArea):
Expand Down
92 changes: 90 additions & 2 deletions src/services/post_service.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from datetime import date, datetime, timezone
import re
from datetime import date, datetime, timezone, timedelta
from typing import Union, Optional

import prawcore
from praw.models.reddit.submission import Submission

import config_loader
from constants import post_constants
from data.post_data import PostData, PostModel
from services import user_service
from services import user_service, sidebar_service
from utils import reddit, discord
from utils.logger import logger

Expand Down Expand Up @@ -156,6 +160,90 @@ def load_post_flairs(subreddit):
logger.debug(f"Flairs loaded: {_flair_colors}")


def rotate_daily_thread(new_post):
"""Update links for the new daily thread and add a sticky comment in the old one."""

reddit_instance = reddit.get_reddit_instance(config_loader.REDDIT["auth"])
subreddit = reddit_instance.subreddit(config_loader.REDDIT["subreddit"])

# Find previous daily thread post
last_week = datetime.now(tz=timezone.utc) - timedelta(days=8)
post_list = _post_data.get_posts_by_username(post_constants.DAILY_THREAD_AUTHOR, last_week.isoformat())
# Filter out the new posts, removed posts, and posts with other flairs.
post_list = filter(lambda p: p.flair_text == "Daily" and p.id36 != new_post.id36 and not p.removed, post_list)
post_list = sorted(post_list, key=lambda p: p.created_time, reverse=True) # sort with most recent first

if not post_list:
old_post = None
logger.warning("Unable to find previous daily thread")
else:
old_post = post_list[0]

# Update sidebar/menu items
sidebar_service.replace_sidebar_link(
post_constants.DAILY_THREAD_NAME, reddit.make_relative_link(new_post), subreddit
)
sidebar_service.update_redesign_menus(
post_constants.DAILY_THREAD_NAME, post_constants.DAILY_THREAD_SHORT_NAME, new_post, subreddit
)

# Update new thread body
original_text = new_post.body
# Change redd.it/<id> links to relative /comments/<id>
updated_text = re.sub(r"https?://(?:www\.)?redd\.it/(\w+)/?", r"/comments/\g<1>", original_text)
# Include link to previous thread, using the thread pulled from database rather than existing link in the post
if old_post:
updated_text = re.sub(
r"\[« Previous Thread]\(.*?\)", f"[« Previous Thread](/comments/{old_post.id36})", updated_text
)
# Keep redesign/mobile image embed after edit
updated_text = re.sub(r"\[(.*?)]\(https://preview\.redd\.it/(\w+)\..*?\)", r'![img](\g<2> "\g<1>")', updated_text)
new_thread = reddit_instance.submission(id=new_post.id36)
new_thread.edit(body=updated_text)

# Sort by new (since it's broken on reddit's end at times)
new_thread.mod.suggested_sort(sort="new")

# Add sticky comment for the new thread (if it exists)
sticky_comment_wiki = subreddit.wiki["daily_thread/sticky_comment"]
try:
sticky_comment_text = sticky_comment_wiki.content_md.strip()
except prawcore.exceptions.NotFound:
sticky_comment_text = ""

if sticky_comment_text:
new_sticky_comment = new_thread.reply(sticky_comment_text)
new_sticky_comment.disable_inbox_replies()
new_sticky_comment.mod.distinguish(sticky=True)
logger.debug("Posted sticky comment to new thread")
else:
logger.debug("No sticky comment for new thread")

# Update old thread
if old_post:
original_text = old_post.body
updated_text = re.sub(r"\[Next Thread »]\(.*?\)", f"[Next Thread »](/comments/{new_post.id36})", original_text)
# Keep redesign/mobile image embed after edit, e.g. ![img](vu9tn0wcvwka1 "This is the place!")
updated_text = re.sub(
r"\[(.*?)]\(https://preview\.redd\.it/(\w+)\..*?\)", r'![img](\g<2> "\g<1>")', updated_text
)
old_thread = reddit_instance.submission(id=old_post.id36)
old_thread.edit(body=updated_text)

# Notify old daily that the new daily is up
notify_comment = old_thread.reply(
f"""
Hello /r/anime, a new daily thread has been posted! Please follow
[this link](/comments/{new_post.id36}) to move on to the new thread
or [search for the latest thread](/r/{subreddit}/search?q=flair%3ADaily&restrict_sr=on&sort=new).
[](#heartbot "And don't forget to be nice to new users!")
"""
)
notify_comment.disable_inbox_replies()
notify_comment.mod.distinguish(sticky=True)


def _create_post_model(reddit_post: Submission) -> PostModel:
"""
Populate a new PostModel based on the Reddit thread.
Expand Down
148 changes: 148 additions & 0 deletions src/services/sidebar_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import re

import praw.models

import config_loader
from constants import post_constants
from utils import reddit
from utils.logger import logger


def replace_sidebar_link(name, new_link, subreddit=None):
"""
Updates the sidebar text by replacing links to `name` with a permalink
to `new_link`. Links formatting is defined by reddit.LINK_REGEX and LINK_FORMAT.
For example, the default format for links is `'[{name}](.*)'`
:param name: text of link to be changed
:param new_link: new link to be set
:param subreddit: PRAW subreddit instance
:return:
"""

pattern_match = post_constants.LINK_REGEX.format(name=name)
pattern_replace = post_constants.LINK_FORMAT.format(name=name, link=new_link)

if not subreddit:
subreddit = reddit.get_reddit_instance(config_loader.REDDIT["auth"]).subreddit(
config_loader.REDDIT["subreddit"]
)
sidebar = subreddit.wiki["config/sidebar"]
sidebar_text = sidebar.content_md
sidebar_updated_text = _replace_text(pattern_match, pattern_replace, sidebar_text)

if sidebar_updated_text is None:
logger.debug("No change necessary")
else:
sidebar.edit(content=sidebar_updated_text, reason=f"Changed link for {name}")
logger.debug("Changes saved to sidebar")


def update_redesign_menus(name, short_name, post, subreddit=None):
"""
Updates the menu and widget text on Redesign by replacing links to
`name` with a permaling to `post`. Links formatting is identical to the
formatting used on old Reddit.
:param name: name of the link to format
:param short_name: name of the link to use in topbar menu (max 20 characters)
:param post: post which should be linked to
:param subreddit: PRAW subreddit instance
"""
logger.debug("Updating menus on Redesign")

assert len(short_name) <= 20

if not subreddit:
subreddit = reddit.get_reddit_instance(config_loader.REDDIT["auth"]).subreddit(
config_loader.REDDIT["subreddit"]
)

topmenu = _get_updated_redesign_topmenu(short_name, reddit.make_permalink(post, shortlink=True), subreddit)
if topmenu is None:
logger.debug("Error updating topmenu")
else:
topmenu.mod.update(data=list(topmenu))
logger.debug("Topbar menu updated")

pattern_match = post_constants.LINK_REGEX.format(name=name)
pattern_replace = post_constants.LINK_FORMAT.format(name=name, link=reddit.make_permalink(post, shortlink=True))

sidemenu = _get_redesign_sidemenu(name, subreddit)
sidemenu_text = sidemenu.text
sidemenu_updated_text = _replace_text(pattern_match, pattern_replace, sidemenu_text)

if sidemenu_updated_text is None:
logger.debug("No change necessary")
else:
sidemenu.mod.update(text=sidemenu_updated_text)
logger.debug("Sidebar widget updated")


def _get_updated_redesign_topmenu(name, new_url, subreddit):
"""
Update the menu by replacing links labeled `name` with `new_url` and
return the updated menu. Updates are *not* reflected to the subreddit
by calling this method.
:param name: text of the menulink to update
:param new_url: replacement url
:param subreddit: PRAW subreddit instance
"""
menu = subreddit.widgets.topbar[0]
assert isinstance(menu, praw.models.Menu)

for item in menu:
if isinstance(item, praw.models.MenuLink):
if item.text == name:
logger.debug(f"Found replaceable MenuLink: {item.text}")
item.url = new_url
elif isinstance(item, praw.models.Submenu):
for subitem in item:
if isinstance(subitem, praw.models.MenuLink):
if subitem.text == name:
logger.debug(f"Found replaceable MenuLink: {item.text}")
subitem.url = new_url
else:
logger.debug(f"Wrong type found searching for MenuLink: {item.__class__}")
else:
logger.debug(f"Wrong type found searching for MenuLink: {item.__class__}")

return menu


def _get_redesign_sidemenu(name, subreddit):
"""
Return the sidebar widget containing a link to `name`.
:param name: name of the link to update
"""
sidebar = subreddit.widgets.sidebar

pattern_match = post_constants.LINK_REGEX.format(name=name)

for widget in sidebar:
if isinstance(widget, praw.models.TextArea):
matches = re.findall(pattern_match, widget.text)
if matches:
logger.debug(f"Found matching side widget '{widget.shortName}'")
return widget

logger.debug("Found no sidebar widget with replaceable match")


def _replace_text(pattern_match, pattern_replace, text):
matches = re.findall(pattern_match, text)
if not matches:
logger.debug("Found no replaceable match")
return None

logger.debug(
"Found replaceable matches\n"
+ f'\t\t\tOld text: {" // ".join(matches)}\n'
+ f"\t\t\tNew text: {pattern_replace}"
)

text_replaced = re.sub(pattern_match, pattern_replace, text)
return text_replaced
22 changes: 21 additions & 1 deletion src/utils/reddit.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def base36decode(number):
return int(number, 36)


def make_permalink(model: "BaseModel") -> str:
def make_permalink(model: "BaseModel", shortlink=False) -> str:
# Avoiding circular imports.
from data.comment_data import CommentModel
from data.post_data import PostModel
Expand All @@ -69,6 +69,8 @@ def make_permalink(model: "BaseModel") -> str:
return base_url + f"comments/{base36encode(model.post_id)}/-/{model.id36}"

if isinstance(model, PostModel):
if shortlink:
return f"https://redd.it/{model.id36}"
return base_url + f"comments/{model.id36}"

if isinstance(model, UserModel):
Expand All @@ -77,6 +79,24 @@ def make_permalink(model: "BaseModel") -> str:
raise TypeError(f"Unknown model {model.__class__}")


def make_relative_link(model: "BaseModel") -> str:
# Avoiding circular imports.
from data.comment_data import CommentModel
from data.post_data import PostModel
from data.user_data import UserModel

if isinstance(model, CommentModel):
return f"/comments/{base36encode(model.post_id)}/-/{model.id36}"

if isinstance(model, PostModel):
return f"/comments/{model.id36}"

if isinstance(model, UserModel):
return f"/u/{model.username}"

raise TypeError(f"Unknown model {model.__class__}")


def get_reddit_instance(config_dict: dict):
"""
Initialize a reddit instance and return it.
Expand Down

0 comments on commit 9819e3c

Please sign in to comment.