Files
PointsBot/pointsbot/bot.py

166 lines
5.7 KiB
Python
Raw Normal View History

2020-01-15 11:07:13 -08:00
import re
import praw
2020-02-01 00:23:15 -08:00
from . import config, database, level, reply
2020-01-15 17:42:49 -08:00
2020-02-03 18:53:23 -08:00
### Globals ###
USER_AGENT = 'PointsBot (by u/GlipGlorp7)'
2020-02-04 17:25:41 -08:00
# The pattern that determines whether a post is marked as solved
# Could also just use re.IGNORECASE flag
SOLVED_PAT = re.compile('![Ss]olved')
MOD_SOLVED_PAT = re.compile('/[Ss]olved')
2020-02-03 18:53:23 -08:00
TEST_COMMENTS = False
2020-01-15 11:07:13 -08:00
### Main Function ###
def run():
2020-02-04 23:21:53 -08:00
cfg = config.load()
2020-02-01 00:23:15 -08:00
levels = cfg.levels
2020-01-15 17:42:49 -08:00
2020-02-03 18:53:23 -08:00
reddit = praw.Reddit(client_id=cfg.client_id,
client_secret=cfg.client_secret,
username=cfg.username,
password=cfg.password,
user_agent=USER_AGENT)
subreddit = reddit.subreddit(cfg.subreddit)
2020-02-01 00:23:15 -08:00
print_level(0, f'Connected to Reddit as {reddit.user.me()}')
print_level(1, f'Read-only? {reddit.read_only}')
print_level(0, f'Watching subreddit {subreddit.title}')
is_mod = bool(subreddit.moderator(redditor=reddit.user.me()))
print_level(1, f'Is mod? {is_mod}')
2020-02-03 18:53:23 -08:00
if TEST_COMMENTS:
make_comments(subreddit, levels)
return
2020-02-01 00:23:15 -08:00
db = database.Database(cfg.database_path)
2020-01-15 11:07:13 -08:00
# Monitor new comments for confirmed solutions
2020-02-03 18:53:23 -08:00
# Passing pause_after=0 will bypass the internal exponential delay; instead,
# have to check if any comments are returned with each query
2020-02-01 00:23:15 -08:00
for comm in subreddit.stream.comments(skip_existing=True, pause_after=0):
if comm is None:
continue
print_level(0, '\nFound comment')
print_level(1, f'Comment text: "{comm.body}"')
2020-01-15 11:07:13 -08:00
2020-02-04 17:25:41 -08:00
if marks_as_solved(comm):
if not is_first_solution(comm):
2020-01-15 11:07:13 -08:00
# Skip this "!solved" comment and wait for the next
2020-02-01 00:23:15 -08:00
print_level(1, 'Not the first solution')
2020-01-15 11:07:13 -08:00
continue
2020-02-01 00:23:15 -08:00
print_level(1, 'This is the first solution')
2020-01-15 11:07:13 -08:00
print_solution_info(comm)
2020-02-01 00:23:15 -08:00
solver = comm.parent().author
print_level(1, f'Adding point for {solver.name}')
2020-01-15 17:42:49 -08:00
db.add_point(solver)
points = db.get_points(solver)
2020-02-01 00:23:15 -08:00
print_level(1, f'Points for {solver.name}: {points}')
level_info = level.user_level_info(points, levels)
2020-01-15 11:07:13 -08:00
2020-02-01 00:23:15 -08:00
# Reply to the comment marking the submission as solved
reply_body = reply.make(solver, points, level_info)
print_level(1, f'Replying with: "{reply_body}"')
comm.reply(reply_body)
2020-01-15 11:07:13 -08:00
# Check if (non-mod) user flair should be updated to new level
2020-02-03 18:53:23 -08:00
lvl = level_info.current
if lvl and lvl.points == points:
print_level(1, f'User reached level: {lvl.name}')
2020-02-01 00:23:15 -08:00
if not subreddit.moderator(redditor=solver):
print_level(2, 'Setting flair')
2020-02-03 18:53:23 -08:00
print_level(3, f'Flair text: {lvl.name}')
print_level(3, f'Flair template ID: {lvl.flair_template_id}')
subreddit.flair.set(solver,
text=lvl.name,
flair_template_id=lvl.flair_template_id)
2020-02-01 00:23:15 -08:00
else:
print_level(2, 'Solver is mod; don\'t alter flair')
2020-01-15 11:07:13 -08:00
else:
2020-02-01 00:23:15 -08:00
print_level(1, 'Not a "!solved" comment')
2020-01-15 11:07:13 -08:00
2020-02-01 00:23:15 -08:00
### Reddit Comment Functions ###
2020-01-15 11:07:13 -08:00
2020-02-04 17:25:41 -08:00
def marks_as_solved(comment):
2020-02-01 00:23:15 -08:00
'''Return True if the comment meets the criteria for marking the submission
as solved, False otherwise.
2020-01-15 11:07:13 -08:00
'''
2020-02-05 10:39:05 -08:00
op_resp_to_solver = (not comment.is_root
and comment.is_submitter
2020-02-04 17:25:41 -08:00
and not comment.parent().is_submitter
and SOLVED_PAT.search(comment.body))
2020-02-05 10:39:05 -08:00
# Mod can only used MOD_SOLVED_PAT on any post, including their own
mod_resp_to_solver = (not comment.is_root
and comment.subreddit.moderator(redditor=comment.author)
2020-02-04 17:25:41 -08:00
and MOD_SOLVED_PAT.search(comment.body))
2020-02-05 10:39:05 -08:00
return op_resp_to_solver or mod_resp_to_solver
2020-01-15 11:07:13 -08:00
2020-02-04 17:25:41 -08:00
def is_first_solution(solved_comment):
2020-02-01 00:23:15 -08:00
# Retrieve any comments hidden by "more comments"
# Passing limit=0 will replace all "more comments"
submission = solved_comment.submission
submission.comments.replace_more(limit=0)
# Search the flattened comments tree
for comment in submission.comments.list():
if (comment.id != solved_comment.id
2020-02-04 17:25:41 -08:00
and marks_as_solved(comment)
2020-02-01 00:23:15 -08:00
and comment.created_utc < solved_comment.created_utc):
# There is an earlier comment for the same submission
# already marked as a solution by the OP
return False
return True
### Debugging & Logging ###
def print_level(num_levels, string):
print('\t' * num_levels + string)
2020-01-15 11:07:13 -08:00
def print_solution_info(comm):
2020-02-01 00:23:15 -08:00
print_level(1, 'Submission solved')
print_level(2, 'Solution comment:')
print_level(3, f'Author: {comm.parent().author.name}')
print_level(3, f'Body: {comm.parent().body}')
print_level(2, '"Solved" comment:')
print_level(3, f'Author: {comm.author.name}')
print_level(3, f'Body: {comm.body}')
2020-01-15 11:07:13 -08:00
2020-02-03 18:53:23 -08:00
def make_comments(subreddit, levels):
2020-02-01 00:48:14 -08:00
testpoints = [1, 3, 5, 10, 15, 30, 45, 75] + list(range(100, 551, 50))
for sub in subreddit.new():
if sub.title == 'Testing comment scenarios':
redditor = sub.author
for points in testpoints:
body = f'Solver: {redditor}\n\nTotal points after solving: {points}'
print_level(0, body)
comm = sub.reply(body)
if comm:
level_info = level.user_level_info(points, levels)
body = reply.make(redditor, points, level_info)
comm.reply(body)
else:
print_level(1, 'ERROR: Unable to comment')
break