Advertisement
CrashARuntimeToday

VirtueTron9000 v0.3.9

Jul 14th, 2018
260
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 27.78 KB | None | 0 0
  1. #!/usr/bin/python
  2. LICENCE = "WTFPL", "http://www.wtfpl.net/about/"
  3. VERSION = "v0.3.9"
  4.  
  5. import logging
  6. import pickle
  7. import praw
  8. import prawcore
  9. import random
  10. import shelve
  11. import string
  12. import sys
  13.  
  14. from datetime import datetime, timedelta
  15. from math import ceil
  16. from time import sleep
  17. from random import randint
  18. from statistics import mean
  19.  
  20. # Set up logging facilities
  21. log = logging.getLogger("VirtueTron")
  22. log.setLevel(logging.DEBUG)
  23. formatter = logging.Formatter(fmt="%(asctime)s - %(levelname)s - %(message)s", datefmt="%Y/%m/%d %I:%M:%S%p")
  24. file_log = logging.FileHandler(filename="VirtueTron.log", mode="a")
  25. file_log.setLevel(logging.INFO)
  26. file_log.setFormatter(formatter)
  27. log.addHandler(file_log)
  28. console_log = logging.StreamHandler(stream=sys.stdout)
  29. console_log.setLevel(logging.DEBUG)
  30. console_log.setFormatter(formatter)
  31. log.addHandler(console_log)
  32. log.info("VirtueTron® 9000™ {0} © CrashARuntimeToday@outlook.com".format(VERSION))
  33. # Connect to Reddit
  34. credentials = pickle.load(open("credentials.pickle", "rb")) # { client_id: "VirtueTron9000", client_secret: "🤖🤡🍆💯™", username: "SignalAVirtueToday", password: "https://youtu.be/RCVJ7bujnSc"}
  35. credentials["user_agent"] = "VirtueTron 9000 {0}".format(VERSION)
  36. reddit = praw.Reddit(**credentials)
  37. tbp = reddit.subreddit("TheBluePill")
  38. log.info("Hello, Reddit!")
  39. # Define some constants
  40. NICE_LIST = []
  41. NAUGHTY_LIST = ["theredpill", "marriedredpill", "mgtow", "braincels", "asktrp", "askmrp", "redpillwomen", "redpillwives", "cringeanarchy", "the_donald", "rpchristians", "pussypassdenied", "mensrights", "milliondollarextreme", "4chan", "whereareallthegoodmen"]
  42. IMMUTABLE_FLAIRS = ["vanguard", "vexatious", "endorsedflair", "alpha", "betaasfuck", "feeemale", "purged"]
  43. BAD_FLAIRS = ["purged", "vexatious"]
  44. GOOD_FLAIRS = ["endorsedflair", "vanguard", "alpha", "betaasfuck", "feeemale"]
  45. TODAYS_THREAT_LEVEL = {"tlsevere":"Severe", "tlhigh":"High", "tlelevated":"Elevated", "tlguarded":"Guarded", "tllow":"Low"}
  46. THREAT_MATRIX = {"tllow": BAD_FLAIRS + ["hb{0}".format(x) for x in range(1,7)],
  47.                  "tlguarded": BAD_FLAIRS + ["hb{0}".format(x) for x in range(1,5)],
  48.                  "tlelevated": BAD_FLAIRS + ["hb{0}".format(x) for x in range(1,2)],
  49.                  "tlhigh": BAD_FLAIRS,
  50.                  "tlsevere": []}
  51. FLAIR_EXEMPT = []
  52. # Helper functions
  53. delay = 2
  54. last_fuckup = None
  55. def praw_fucked_up():
  56.     global delay
  57.     global last_fuckup
  58.     log.error("Reddit API error: waiting {0} seconds".format(delay))
  59.     try:
  60.         if delay > 128 or datetime.now() > last_fuckup + timedelta(minutes=2): delay = 2
  61.     except TypeError:
  62.         delay = 2
  63.     sleep(delay)
  64.     delay *= 2
  65.     last_fuckup = datetime.now()
  66.  
  67. def is_user_valid(name):
  68.     try:
  69.         for comment in reddit.redditor(name).new(limit=1):
  70.             log.debug("User: {0} still exists (most recent comment id is {1})".format(name, comment.id))
  71.         return True
  72.     except (prawcore.exceptions.NotFound, prawcore.exceptions.Forbidden):
  73.         return False
  74.  
  75. def drop_user(name):
  76.     log.info("User: {0} is deleted or suspended, dropping from userlist".format(name))
  77.     del users[name]
  78.     del user_data[name]
  79.  
  80. def get_flair(name):
  81.     if is_user_valid(name):
  82.         while True:
  83.             try:
  84.                 for flair in tbp.flair(redditor=name):
  85.                     return flair["flair_css_class"]
  86.             except prawcore.PrawcoreException:
  87.                 praw_fucked_up()
  88.     else:
  89.         return False
  90.  
  91. def set_flair(name, flair_text, flair_css):
  92.     if is_user_valid(name):
  93.         while True:
  94.             try:
  95.                 tbp.flair.set(name, flair_text, flair_css)
  96.                 return True
  97.             except prawcore.PrawcoreException:
  98.                 praw_fucked_up()
  99.     else:
  100.         return False
  101.  
  102. def set_link_flair(submission, flair_text, flair_css):
  103.     while True:
  104.         try:
  105.             submission.mod.flair(css_class=flair_css, text=flair_text)
  106.             return
  107.         except prawcore.PrawcoreException:
  108.             praw_fucked_up()
  109.  
  110. def add_approved(name):
  111.     while True:
  112.         try:
  113.             tbp.contributor.add(name)
  114.             return True
  115.         except praw.exceptions.APIException as e:
  116.             if e.error_type == "USER_DOESNT_EXIST":
  117.                 drop_user(name)
  118.                 return False
  119.         except prawcore.PrawcoreException:
  120.             praw_fucked_up()
  121.  
  122. def del_approved(name):
  123.     while True:
  124.         try:
  125.             tbp.contributor.remove(name)
  126.             return True
  127.         except praw.exceptions.APIException as e:
  128.             if e.error_type == "USER_DOESNT_EXIST":
  129.                 drop_user(name)
  130.             return False
  131.         except prawcore.PrawcoreException:
  132.             praw_fucked_up()
  133.  
  134. #Shit tests
  135. def scrabble_get_draw(scrabble):
  136.     BURN_RATE = 3
  137.     VOWELS = ['u', 'a', 'i', 'o', 'e']
  138.     if len(scrabble["safe_letters"]) < BURN_RATE:
  139.         return False
  140.     new_len = len(scrabble["safe_letters"]) + BURN_RATE
  141.     letter = random.choice(scrabble["safe_letters"])
  142.     scrabble["safe_letters"].remove(letter)
  143.     scrabble["banned_letters"].append(letter)
  144.     if letter not in VOWELS:
  145.         while len(scrabble["safe_letters"]) < new_len:
  146.             letter = random.choice(scrabble["safe_letters"])
  147.             if letter not in VOWELS:
  148.                 scrabble["safe_letters"].remove(letter)
  149.                 scrabble["banned_letters"].append(letter)
  150.     scrabble["banned_letters"] = sorted(scrabble["banned_letters"])
  151.     return scrabble
  152.  
  153. def scrabble_test(user, comment):
  154.     # Start the game if the user isn't already playing scrabble
  155.     if "scrabble" not in user.data["comment_eater"].keys():
  156.         scrabble = {"start": datetime.now(), "banned_letters": [], "safe_letters": string.ascii_lowercase, "eaten": []}
  157.         scrabble_get_draw(scrabble)
  158.         reply = comment.reply("As an unmutual member of our community, you've been chosen for a shit test:\n\n Your comments are no longer allowed to contain the letters: {0}.\n\n If you manage to run out of letters within a week, you will be hard nexted.\n\n**Good luck!**\n\n*NOTE: This is an automated action, but this account is also attached to a human operator if you have any concerns. We appreciate your continued business!*".format(scrabble["letters"]))
  159.         reply.mod.distinguish()
  160.         log.info("User: {0} now playing scrabble! Banned letters are {1}".format(user.name, scrabble["letters"]))
  161.         user.data["comment_eater"]["scrabble"] = scrabble
  162.         user_data[user.name] = user.data
  163.         return
  164.     # Stop the game if the time limit's up
  165.     scrabble = user.data["comment_eater"]["scrabble"]
  166.     if datetime.now() > scrabble["start"] + timedelta(weeks=1):
  167.         reply = comment.reply("You've successfully passed your shit test! Congratulations! As recompense you will receive a token SMV boost on r/TheBluePill.\n\n**Thanks for playing!**")
  168.         reply.mod.distinguish()
  169.         user.data["manual_score_bias"] += 3
  170.         del user.data["comment_eater"]["scrabble"]
  171.         user_data[user.name] = user.data
  172.         return
  173.     # Continue the game
  174.     body = comment.body.lower()
  175.     if comment.id not in scrabble["eaten"] and any(letter in body for letter in scrabble["banned_letters"]):
  176.         scrabble = scrabble_get_draw(scrabble)
  177.         if scrabble == False: # Out of letters to draw, test has been failed
  178.             mesg = "Oh, no! It looks like you've run out of letters. This game is over.\n\n**Thanks for playing!**"
  179.             reply = comment.reply(mesg)
  180.             tbp.banned.add(user.name, ban_reason="AUTOBANNED: failed at scrabble", ban_message=mesg)
  181.             set_flair(user.name, "PURGED", "purged")
  182.             del user.data["comment_eater"]["scrabble"]
  183.             user.data["flair"] = "purged"
  184.             user_data[user.name] = user.data
  185.             log.warn("User: {0} is purged (failed at scrabble)".format(user.name))
  186.             return
  187.         reply = comment.reply("Whoops! It looks like you used a banned letter. Your banned letters are now: {0}\n\n**Better luck next time!**".format(scrabble["letters"]))
  188.         reply.mod.distinguish()
  189.         if not comment.approved:
  190.             scrabble["eaten"].append(comment.id)
  191.             comment.mod.remove()
  192.             log.info("Removing comment by user: {0} ({1}) [id: {2}] on submission '{3}' [id: {4}], failed scrabble test. Banned letters are now {5}".format(user.name, user.flair, comment.id, comment.submission.title, comment.submission.id, scrabble["banned_letters"]))
  193.         user.data["comment_eater"]["scrabble"] = scrabble
  194.         user_data[user.name] = user.data
  195.  
  196. def brevity_test(user, comment):
  197.     if "brevity" not in user.data["comment_eater"].keys():
  198.         brevity = {"start": datetime.now(), "length": 160, "eaten": []}
  199.         #MESSAGE USER ABOUT TEST
  200.         user.data["comment_eater"]["brevity"] = brevity
  201.         user_data[user.name] = user.data
  202.         return
  203.     brevity = user.data["comment_eater"]["brevity"]
  204.     if len(comment.body) > brevity["length"]:
  205.         brevity["length"] /= 2
  206.         if brevity["length"] < 2:
  207.             #BREVITY TEST FAILED MESSAGE, BAN USER (MAYBE DO SOME CLEANUP)
  208.             return
  209.         #WARN USER ABOUT FAILURE/ESCALATION, remove offending comment
  210.         user_data[user.name] = user.data
  211. SHIT_TESTS = {"scrabble": scrabble_test, "brevity": brevity_test}    
  212.  
  213. class Tracker:
  214.     SCAN_INTERVAL = timedelta(minutes=10)
  215.     SCAN_DEPTH = 8
  216.     def __init__(self, name, new_user=True):
  217.         self.name = name
  218.         if new_user:
  219.             self.data = {   "name": name,
  220.                             "scan_count": 0,
  221.                             "last_seen": None,
  222.                             "next_scan": None,
  223.                             "smv": 0,
  224.                             "flair": None,
  225.                             "good_score": [],
  226.                             "bad_score": [],
  227.                             "manual_score_bias": 0,
  228.                             "best_post": { "score": 0, "id": None },
  229.                             "worst_post": { "score": 0, "id": None },
  230.                             "comment_eater": {}
  231.                         }  
  232.             self.update()
  233.             log.info("Adding new user: {0}".format(name))
  234.         else:
  235.             self.data = user_data[name]
  236.     # Some simple helper functions
  237.     @property
  238.     def good_score(self):
  239.         if len(self.data["good_score"]) > 0:
  240.             return mean(self.data["good_score"]) + self.data["manual_score_bias"]
  241.         else:
  242.             log.warn("User: {0} has no tracked good points?".format(self.name))
  243.             return 0
  244.     @property
  245.     def bad_score(self):
  246.         if len(self.data["bad_score"]) > 0:
  247.             return mean(self.data["bad_score"])
  248.         else:
  249.             log.warn("User: {0} has no tracked bad points?".format(self.name))
  250.             return 0
  251.     @property
  252.     def score(self):
  253.         return self.good_score + self.bad_score
  254.     def update(self):
  255.         self.data["last_seen"] = datetime.now()
  256.         try:
  257.             if self.data["last_seen"] < self.data["next_scan"]:
  258.                 log.debug("Skipping user: {0} (next scan on {1:%Y/%m/%d %I:%M:%S%p})".format(self.name, self.data["next_scan"]))
  259.                 return True
  260.         except TypeError:
  261.             pass
  262.         good, bad, good_count, bad_count = 0, 0, 0, 0
  263.         try:
  264.             for comment in reddit.redditor(self.name).comments.new(limit=100):
  265.                 sub = comment.subreddit.display_name.lower()
  266.                 karma = comment.score - 1
  267.                 if sub == "thebluepill":
  268.                     good += karma
  269.                     good_count += 1
  270.                     try:
  271.                         if karma > self.data["best_post"]["score"]:
  272.                             self.data["best_post"] = {"score": karma, "id": comment.id}
  273.                     except TypeError:
  274.                         self.data["best_post"] = {"score": karma, "id": comment.id}
  275.                     try:
  276.                         if karma < self.data["worst_post"]["score"]:
  277.                             self.data["worst_post"] = {"score": karma, "id": comment.id}
  278.                     except TypeError:
  279.                         self.data["worst_post"] = {"score": karma, "id": comment.id}
  280.                 elif sub in NICE_LIST:
  281.                     good += karma / 2
  282.                 elif sub in NAUGHTY_LIST and karma > 1:
  283.                     bad -= karma
  284.                     bad_count += 1
  285.             if good_count > 0:
  286.                 good /= good_count
  287.                 good *= good_count / 10 # Every 20 posts in r/TheBluePill doubles your score
  288.             if bad_count > 0:
  289.                 bad /= bad_count
  290.                 bad *= bad_count / 5 # Every 10 posts in NAUGHTY_LIST doubles your bad score
  291.                 # Are they vexatious? (Of course they are!)
  292.                 if self.data["flair"] != "vexatious" and bad_count > 10 and abs(bad) > good:
  293.                     log.info("User: {0} is vexatious ({1} posts in NAUGHTY_LIST)".format(self.name, bad_count))
  294.                     set_flair(self.name, "VEXATIOUS LITIGANT", "vexatious")
  295.                     self.data["flair"] = "vexatious"
  296.             self.data["good_score"].insert(0, good)
  297.             if len(self.data["good_score"]) > Tracker.SCAN_DEPTH:
  298.                 self.data["good_score"].pop()
  299.                 log.debug("Dropping oldest good karma sample for user: {0}".format(self.name))
  300.             self.data["bad_score"].insert(0, bad)
  301.             if len(self.data["bad_score"]) > Tracker.SCAN_DEPTH:
  302.                 self.data["bad_score"].pop()
  303.                 log.debug("Dropping oldest bad karma sample for user: {0}".format(self.name))
  304.             self.data["scan_count"] += 1
  305.             self.data["next_scan"] = self.data["last_seen"] + Tracker.SCAN_INTERVAL
  306.             log.info("Scanned user: {0} (scanned {1} times), good_karma: {2:.2f} ({3} comments), bad_karma: {4:.2f} ({5} comments)".format(self.name, self.data["scan_count"], good, good_count, bad, bad_count))
  307.             user_data[self.name] = self.data
  308.             return True
  309.         except (prawcore.exceptions.NotFound, prawcore.exceptions.Forbidden):
  310.             return False
  311.  
  312. def update_flairs():
  313.     log.info("Recalculating SMV")
  314.     i = 0
  315.     rank_count = 0
  316.     rank_lowest = 0
  317.     rank_highest = 0
  318.     last_smv = 0
  319.     total = len(users)
  320.     # I'm so fuckin' dumb, we should've been doing it this way from the beginning
  321.     # (grabbing the entire lists of purged/approved users at the start and checking locally instead of asking Reddit if individual users are purged/approved)
  322.     purged_users = list(tbp.banned(limit=None))
  323.     approved_users = list(tbp.contributor(limit=None))
  324.     log.info("TBP has {0} purged users and {1} approved users ({2} active users tracked)".format(len(purged_users), len(approved_users), total))
  325.     for user in sorted(users.values(), key=lambda user: user.score):
  326.         dirty = False
  327.         flair = user.data["flair"]
  328.         # Check if user should be marked/unmarked as purged
  329.         if flair != "purged" and user.name in purged_users:
  330.             log.info("Marking user: {0} purged".format(user.name))
  331.             set_flair(user.name, "PURGED", "purged")
  332.             flair = "purged"
  333.             user.data["flair"] = "purged"
  334.             dirty = True
  335.         elif flair == "purged" and user.name not in purged_users:
  336.             log.info("User: {0} was unbanned, correcting flair".format(user.name))
  337.             set_flair(user.name, "", "")
  338.             flair = None
  339.             user.data["flair"] = None
  340.             dirty = True
  341.         # Calculate SMV
  342.         i += 1
  343.         smv = ceil((i / total) * 10)
  344.         if smv > last_smv:
  345.             if not last_smv == 0:
  346.                 log.info("{0} Users at rank hb{1} (lowest: {2} at {3:.2f}, highest: {4} {5:.2f}), now processing rank hb{6}".format(rank_count, last_smv, rank_lowest_user, rank_lowest, rank_highest_user, rank_highest, smv))
  347.             last_smv = smv
  348.             rank_count = 0
  349.             rank_lowest = user.score
  350.             rank_lowest_user = user.name
  351.         rank_count += 1
  352.         rank_highest = user.score
  353.         rank_highest_user = user.name
  354.         if not flair:
  355.             log.debug("No cached flair for user: {0}, checking Reddit".format(user.name))
  356.             flair = get_flair(user.name)
  357.             if flair == False:
  358.                 drop_user(user.name)
  359.                 continue
  360.             if flair != None: # Don't update tracked flair if user isn't wearing a flair
  361.                 user.data["flair"] = flair
  362.                 log.debug("Reddit says user: {0}'s flair is {1}".format(user.name, flair))
  363.                 dirty = True
  364.         log.debug("User: {0}, SMV: {1}, score: {2:.2f} (current flair {3})".format(user.name, smv, user.score, flair))
  365.         # Update flairs
  366.         if flair in IMMUTABLE_FLAIRS:
  367.             if "flair" == "vexatious" and smv > 2 and user.good_score > abs(user.bad_score):
  368.                 log.warn("Should user: {0} be vexatious? SMV: {1}, good_score: {2:.2f}, bad_score: {3:.2f}".format(user.name, smv, user.good_karma, user.bad_karma))
  369.             else:
  370.                 log.debug("Not changing user: {0} (immutable flair {1})".format(user.name, flair))
  371.         elif flair != "hb{0}".format(smv):
  372.             flair = "hb{0}".format(smv)
  373.             user.data["flair"] = flair
  374.             user.data["smv"] = smv
  375.             if set_flair(user.name, "Hβ{0}".format(smv), "hb{0}".format(smv)) == False:
  376.                 drop_user(user.name)
  377.             log.info("Updating user: {0} flair to hb{1}".format(user.name, smv))
  378.             dirty = True
  379.         else:
  380.             log.debug("User: {0} still an Hβ{1}".format(user.name, smv))
  381.         # Add/remove approved contributors
  382.         if ((smv > 7 or flair in GOOD_FLAIRS) and flair not in BAD_FLAIRS) and user.name not in approved_users:
  383.             log.debug("Adding approved contributor: {0}".format(user.name))
  384.             add_approved(user.name)
  385.         elif ((smv < 4 or flair in BAD_FLAIRS) and flair not in GOOD_FLAIRS) and user.name in approved_users:
  386.             del_approved(user.name)
  387.             log.info("Removing approved contributor: {0}".format(user.name))
  388.         if dirty:
  389.             user_data[user.name] = user.data
  390.     log.info("{0} Users at rank hb{1} (lowest: {2} at {3:.2f}, highest: {4} {5:.2f})".format(rank_count, last_smv, rank_lowest_user, rank_lowest, rank_highest_user, rank_highest))
  391.  
  392. BETA_TESTERS = "FailAShitTestToday"
  393. def botloop(first_flair_update=datetime.now()):
  394.     DEAD_USER_INTERVAL = timedelta(days=1)
  395.     FLAIR_UPDATE_INTERVAL = timedelta(minutes=15)
  396.     next_flair_update = first_flair_update
  397.     next_dead_user_update = first_flair_update + DEAD_USER_INTERVAL
  398.     try:
  399.         seen_comments = global_data["seen_comments"]
  400.     except KeyError:
  401.         seen_comments = []
  402.     # Loop over comment stream indefinitely
  403.     for comment in tbp.stream.comments():
  404.         if datetime.now() > next_dead_user_update:
  405.             log.info("Dropping dead users")
  406.             user_count = len(users)
  407.             dead_user_count = 0
  408.             for user in list(users.values()):
  409.                 if not is_user_valid(user.name):
  410.                     dead_user_count += 1
  411.                     drop_user(user.name)
  412.             next_dead_user_update = datetime.now() + DEAD_USER_INTERVAL
  413.             log.info("Dead user scan complete: {0} of {1} tracked users dropped (next update due on/after: {2:%Y/%m/%d %I:%M:%S%p})".format(dead_user_count, user_count, next_dead_user_update))
  414.         if datetime.now() > next_flair_update:
  415.             update_flairs()
  416.             next_flair_update = datetime.now() + FLAIR_UPDATE_INTERVAL
  417.             log.debug("Flair update complete (next update due on/after: {0:%Y/%m/%d %I:%M:%S%p})".format(next_flair_update))
  418.         # Don't bother processing the comment if it's already been looked at
  419.         if comment.id in seen_comments:
  420.             log.debug("Skipping comment '{0}' on submission '{1}' [id: {2}], already seen.".format(comment.id, comment.submission.title, comment.submission.id))
  421.             continue
  422.         # Grab submission link flair, assign one randomly if blank
  423.         threat_level = comment.submission.link_flair_css_class
  424.         if threat_level == None:
  425.             if randint(0,1) == 0: # 50-50 shot of getting "Severe"
  426.                 threat_selector = 0
  427.             else:
  428.                 threat_selector = randint(1, len(TODAYS_THREAT_LEVEL) - 3) # Won't randomly assign the last two link flairs
  429.             threat_level = list(TODAYS_THREAT_LEVEL.keys())[threat_selector]
  430.             set_link_flair(comment.submission, TODAYS_THREAT_LEVEL[threat_level], threat_level)
  431.             log.info("Set threat level for submission '{0}' [id: {1}] to {2}".format(comment.submission.title, comment.submission.id, TODAYS_THREAT_LEVEL[threat_level]))
  432.         else:
  433.             log.debug("Threat level for submission '{0}' [id: {1}] is {2})".format(comment.submission.title, comment.submission.id, TODAYS_THREAT_LEVEL[threat_level]))
  434.         # Skip over comments from deleted users
  435.         if comment.author:
  436.             dirty = False
  437.             name = comment.author.name
  438.             author_flair = comment.author_flair_css_class
  439.             # Update tracker
  440.             if not name in users.keys():
  441.                 users[name] = Tracker(name)
  442.             else:
  443.                 if not users[name].update():
  444.                     drop_user(name)
  445.                     continue
  446.             # Update tracked flair if comment flair doesn't match tracked flair
  447.             user_flair = users[name].data["flair"]
  448.             if author_flair and user_flair != author_flair:
  449.                 dirty = True
  450.                 log.info("Updating tracked flair for user: {0} -- was {1}, now {2}".format(name, user_flair, author_flair))
  451.                 user_flair = author_flair
  452.                 users[name].data["flair"] = user_flair
  453.             # Handle unflaired users and their comments
  454.             if not author_flair and users[name].data["scan_count"] > 3:
  455.                 log.debug("User: {0} should have flair: {1}, but comment flair is {2}".format(name, user_flair, author_flair))
  456.                 if "unflaired" not in users[name].data["comment_eater"].keys():
  457.                     users[name].data["comment_eater"]["unflaired"] = {"start":datetime.now(), "eaten": []}
  458.                     reply = comment.reply("Apologies for the inconvenience, but user flairs are mandatory on /r/TheBluePill. Please enable your user flair at your earliest convenience:\n\n- If you're on mobile Reddit, you'll need to first flip back to the desktop site (Burger Menu -> Desktop Site), then:\n- If you're on classic Reddit, you'll need to disable this Subreddit's custom CSS (preferences -> display options -> allow subreddits to show me custom themes)\n- If you're using an app to access Reddit, check the app's documentation, but you'll probably need to access the desktop site from a browser.\n\nYour comments will only be automatically removed if your SMV is too low, or the comment has negative karma. Once you're properly flaired again, these automatically removed comments will be restored.\n\nThanks for choosing r/TheBluePill!\n\netc. etc.,\n\n-Management\n\n*NOTE: This is an automated action, but this account is also attached to a human operator if you have any concerns. We appreciate your continued business!*")
  459.                     reply.mod.distinguish()
  460.                     log.info("Warned user: {0}, flairs are mandatory.".format(name))
  461.                     dirty = True
  462.                 else:
  463.                     unflaired = users[name].data["comment_eater"]["unflaired"]
  464.                     if name in FLAIR_EXEMPT:
  465.                         log.info("Not removing comment {0} by unflaired user: {1} (exempt)".format(comment.id, name))
  466.                     elif datetime.now() < unflaired["start"] + timedelta(minutes=15):
  467.                         log.info("Not removing comment {0} by unflaired user: {1} (still in grace period after warning)".format(comment.id, name))
  468.                     elif users[name].data["smv"] > 3 or comment.score > 0 or user_flair in GOOD_FLAIRS:
  469.                         log.info("Not removing comment {0} by unflaired user: {1} (too high value)".format(comment.id, name))
  470.                     elif comment.approved:
  471.                         log.info("Not removing comment {0} by unflaired user: {1} (manually approved)".format(comment.id, name))
  472.                     elif comment.id in unflaired["eaten"] or comment.removed:
  473.                         log.info("Not removing comment {0} by unflaired user: {1} (already removed)".format(comment.id, name))
  474.                     else:
  475.                         unflaired["eaten"].append(comment.id)
  476.                         comment.mod.remove()
  477.                         log.info("Removed comment by user {0} ({1}) [id: {2}] on submission {3} [id: {4}], not wearing flair!".format(name, author_flair, comment.id, comment.submission.title, comment.submission.id))
  478.                         dirty = True
  479.             elif author_flair and "unflaired" in users[name].data["comment_eater"].keys() and len(users[name].data["comment_eater"]["unflaired"]["eaten"]) > 0:
  480.                 for comment in users[name].data["comment_eater"]["unflaired"]["eaten"]:
  481.                     reddit.comment(id=comment).approve()
  482.                     log.info("Restoring comment {0} for user: {1} (is wearing flair now)".format(comment, name))
  483.                 users[name].data["comment_eater"]["unflaired"]["eaten"] = []
  484.                 dirty = True
  485.             # Remove comments if user's SMV is too low for submission's threat level
  486.             if author_flair in THREAT_MATRIX[threat_level] and name != comment.submission.author and not comment.approved:
  487.                 dirty = True
  488.                 if "low_smv" not in users[name].data["comment_eater"].keys():
  489.                     users[name].data["comment_eater"]["low_smv"] = {"eaten": [comment.id]}
  490.                 else:
  491.                     users[name].data["comment_eater"]["low_smv"]["eaten"].append(comment.id)
  492.                 comment.mod.remove()
  493.                 log.info("Removing comment by user: {0} ({1}) [id: {2}] on submission '{3}' [id: {4}] ({5}), SMV too low!".format(name, author_flair, comment.id, comment.submission.title, comment.submission.id, TODAYS_THREAT_LEVEL[threat_level]))
  494.             #Shit tests
  495.             if name in BETA_TESTERS:
  496.                 shit_test = [shit_test for eater in users[name].data["comment_eater"] if eater in SHIT_TESTS.keys()]
  497.                 if shit_test:
  498.                     if len(shit_test) > 1: # There should only ever be one active shit test
  499.                         log.error("Multiple shit tests assigned to user: {0}, only processing the first")
  500.                     shit_test = shit_test[0]
  501.                     log.debug("Shit testing comment by user: {0} ({1}) [id: {2}] on submission '{3}' [id: {4}]".format(name, author_flair, comment.id, comment.submission.title, comment.submission.id))
  502.                     SHIT_TESTS[shit_test](users[name], comment)
  503.                 else:
  504.                     scrabble_test(users[name], comment)
  505.             if dirty:
  506.                 user_data[name] = users[name].data
  507.         else:
  508.             log.debug("Skipping comment {0} on submission '{1}' [id: {2}], author no longer exists".format(comment.id, comment.submission.title, comment.submission.id))
  509.         seen_comments.insert(0, comment.id)
  510.         if len(seen_comments) > 100:
  511.             seen_comments.pop()
  512.         global_data["seen_comments"] = seen_comments
  513.  
  514. with shelve.open("user_data") as user_data, shelve.open("global_data") as global_data:
  515.     users = {}
  516.     for user in user_data.keys():
  517.         users[user] = Tracker(user, False)
  518.     try:
  519.         botloop()
  520.     except prawcore.PrawcoreException:
  521.         praw_fucked_up()
  522.         botloop()
  523.     except KeyboardInterrupt:
  524.         log.info("VirtuteTron going off-line")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement