Advertisement
CrashARuntimeToday

VirtueTron9000 v0.3.2_pre20

Jul 6th, 2018
236
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.76 KB | None | 0 0
  1. #!/usr/bin/python
  2. LICENCE = "WTFPL", "http://www.wtfpl.net/about/"
  3. VERSION = "v0.3.2_pre20"
  4.  
  5. import logging
  6. import pickle
  7. import praw
  8. import prawcore
  9. import sys
  10. from datetime import datetime, timedelta
  11. from math import ceil
  12. from time import sleep
  13. from random import randint
  14.  
  15. log = logging.getLogger("VirtueTron")
  16. log.setLevel(logging.DEBUG)
  17. formatter = logging.Formatter(fmt="%(asctime)s - %(levelname)s - %(message)s", datefmt="%Y/%m/%d %I:%M:%S%p")
  18. file_log = logging.FileHandler(filename="VirtueTron.log", mode="a")
  19. file_log.setLevel(logging.INFO)
  20. file_log.setFormatter(formatter)
  21. log.addHandler(file_log)
  22. console_log = logging.StreamHandler(stream=sys.stdout)
  23. console_log.setLevel(logging.DEBUG)
  24. console_log.setFormatter(formatter)
  25. log.addHandler(console_log)
  26.  
  27. log.info("Hello, Reddit!")
  28. log.info("VirtueTron® 9000™ {0} © CrashARuntimeToday@outlook.com".format(VERSION))
  29.  
  30. credentials = pickle.load(open("credentials.pickle", "rb")) # {client_id:"VirtueTron9000", client_secret:"🤖🤡🍆💯™", username:"SignalAVirtueToday", password:"https://youtu.be/RCVJ7bujnSc"}
  31. credentials["user_agent"] = "VirtueTron 9000 {0}".format(VERSION)
  32. reddit = praw.Reddit(**credentials)
  33. tbp = reddit.subreddit("TheBluePill")
  34.  
  35. IMMUTABLE_FLAIRS = ["vanguard", "vexatious", "endorsedflair", "alpha", "betaasfuck", "feeemale", "purged"]
  36. BAD_FLAIRS = ["purged", "vexatious"]
  37. GOOD_FLAIRS = ["endorsedflair", "vanguard", "alpha", "betaasfuck", "feeemale"]
  38. TODAYS_THREAT_LEVEL = {"tlsevere":"Severe", "tlhigh":"High", "tlelevated":"Elevated", "tlguarded":"Guarded", "tllow":"Low"}
  39. THREAT_MATRIX = {"tllow": BAD_FLAIRS + ["hb{0}".format(x) for x in range(1,7)],
  40.                  "tlguarded": BAD_FLAIRS + ["hb{0}".format(x) for x in range(1,5)],
  41.                  "tlelevated": BAD_FLAIRS + ["hb{0}".format(x) for x in range(1,3)],
  42.                  "tlhigh": BAD_FLAIRS,
  43.                  "tlsevere": []}
  44. FLAIR_EXEMPT = "sofcknwrong"
  45.  
  46. delay = 2
  47. last_fuckup = None
  48. def praw_fucked_up():
  49.     global delay
  50.     global last_fuckup
  51.     log.warning("Reddit API error: waiting {0} seconds".format(delay))
  52.     try:
  53.         if delay > 128 or datetime.now() > last_fuckup + timedelta(minutes=2): delay = 2
  54.     except TypeError:
  55.         delay = 2
  56.     sleep(delay)
  57.     delay *= 2
  58.     last_fuckup = datetime.now()
  59.  
  60. def get_flair(name):
  61.     while True:
  62.         try:
  63.             for flair in tbp.flair(redditor=name):
  64.                 return flair["flair_css_class"]
  65.         except prawcore.PrawcoreException:
  66.             praw_fucked_up()
  67.  
  68.  
  69. class Tracker:
  70.     NICE_LIST = []
  71.     NAUGHTY_LIST = ["theredpill", "marriedredpill", "mgtow", "braincels", "asktrp", "askmrp", "redpillwomen", "redpillwives", "cringeanarchy", "the_donald", "rpchristians", "pussypassdenied", "mensrights", "milliondollarextreme", "4chan", "whereareallthegoodmen"]
  72.     # For some reason VSCode eats the indents on blank lines and this gives the python interpreter fits when I copy/paste, so empty comments at the correct indent level it is!
  73.     def __init__(self, name, new_user=True, good_points=[0], bad_points=[0], scan_count=0, next_refresh=(datetime.now()+timedelta(minutes=15)), current_flair=None, warned=None, comments_eaten=[], current_shittest=None, last_seen=datetime.now()):
  74.         self.name = name
  75.         self.smv = 0
  76.         self.scan_count = scan_count
  77.         self.next_refresh = next_refresh
  78.         self.current_flair = current_flair
  79.         self.warned = warned
  80.         self.comments_eaten = comments_eaten
  81.         self.good_points = good_points
  82.         self.bad_points = bad_points
  83.         self.best_post_karma = 0
  84.         self.best_post_id = None
  85.         self.worst_post_karma = 0
  86.         self.worst_post_id = None
  87.         self.current_shittest = current_shittest
  88.         self.last_seen = last_seen
  89.         if new_user:
  90.             self.update()
  91.     #
  92.     @property
  93.     def good_karma(self):
  94.         return sum(self.good_points) / len(self.good_points)
  95.     @property
  96.     def bad_karma(self):
  97.         return sum(self.bad_points) / len(self.bad_points)
  98.     @property
  99.     def score(self):
  100.         return self.good_karma + self.bad_karma
  101.     #
  102.     def update(self):
  103.         good, bad, good_count, bad_count = 0, 0, 0, 0
  104.         for comment in reddit.redditor(self.name).comments.new(limit=100):
  105.             sub = comment.subreddit.display_name.lower()
  106.             karma = comment.score - 1
  107.             if sub == "thebluepill":
  108.                 good += karma
  109.                 good_count += 1
  110.                 if karma > self.best_post_karma:
  111.                     self.best_post_karma = karma
  112.                     self.best_post_id = comment.id
  113.                 if karma < self.worst_post_karma:
  114.                     self.worst_post_karma = karma
  115.                     self.worst_post_id = comment.id
  116.             elif sub in Tracker.NICE_LIST:
  117.                 good += (karma) / 2
  118.             elif sub in Tracker.NAUGHTY_LIST and karma > 1:
  119.                 bad -= karma
  120.                 bad_count += 1
  121.         #
  122.         if good_count > 0:
  123.             good /= good_count
  124.         if bad_count > 0:
  125.             bad /= bad_count
  126.             bad *= bad_count / 5 # Every 10 posts in NAUGHTY_LIST doubles your bad karma
  127.             # Are they vexatious? (Of course they are!)
  128.             if self.current_flair != "vexatious" and bad_count > 10 and abs(bad) > good:
  129.                 log.info("User: {0} is vexatious ({1} posts in NAUGHTY_LIST)".format(self.name, bad_count))
  130.                 tbp.flair.set(self.name, "VEXATIOUS LITIGANT", "vexatious")
  131.                 self.current_flair = "vexatious"
  132.         #
  133.         log.info("Scanned user: {0}, good_karma: {1:.2f} ({2} comments), bad_karma: {3:.2f} ({4} comments)".format(self.name, good, good_count, bad, bad_count))
  134.         self.good_points.insert(0, good)
  135.         if len(self.good_points) > 8:
  136.             self.good_points.pop()
  137.             log.debug("Dropping oldest good karma sample for user: {0}".format(self.name))
  138.         self.bad_points.insert(0, bad)
  139.         if len(self.bad_points) > 8:
  140.             self.bad_points.pop()
  141.             log.debug("Dropping oldest bad karma sample for user: {0}".format(self.name))
  142.         self.scan_count += 1
  143.         self.next_refresh = datetime.now() + timedelta(minutes=15)
  144.  
  145. def update_flairs():
  146.     log.info("Recalculating SMV")
  147.     i = 0
  148.     total = len(users)
  149.     # I'm so fuckin' dumb, we should've been doing it this way from the beginning
  150.     purged_users = list(tbp.banned(limit=None))
  151.     approved_users = list(tbp.contributor(limit=None))
  152.     #
  153.     for user in sorted(users.values(), key=lambda x: x.score):
  154.         if user.name in purged_users and user.current_flair != "purged":
  155.             log.info("Marking user: {0} purged".format(user.name))
  156.             tbp.flair.set(user.name, "PURGED", "purged")
  157.             user.current_flair = "purged"
  158.         elif user.current_flair == "purged" and user.name not in purged_users:
  159.             log.info("User: {0} was unbanned, correcting flair".format(user.name))
  160.             tbp.flair.set(user.name, "", "")
  161.             user.current_flair = ""
  162.         #
  163.         i += 1
  164.         user.smv = ceil((i / total) * 10)
  165.         log.debug("User: {0}, SMV: {1}, score: {2:.2f} (current flair {3})".format(user.name, user.smv, user.score, user.current_flair))
  166.         #
  167.         if user.current_flair == None:
  168.             # Check if user deleted their account
  169.             try:
  170.                 for x in reddit.redditor(user.name).new(limit=1):
  171.                     y = x
  172.             except (prawcore.exceptions.NotFound, prawcore.exceptions.Forbidden):
  173.                 log.info("User: {0} is deleted, dropping from userlist".format(user.name))
  174.                 del users[user.name]
  175.                 total -= 1
  176.                 continue
  177.             log.debug("No cached flair for user: {0}, checking Reddit".format(user.name))
  178.             user.current_flair = get_flair(user.name)
  179.             log.debug("Reddit says user: {0}'s flair is {1}".format(user.name, user.current_flair))
  180.         #
  181.         if user.current_flair in IMMUTABLE_FLAIRS:
  182.             if user.current_flair == "vexatious" and user.smv > 2 and user.good_karma > abs(user.bad_karma):
  183.                 log.warn("Should user: {0} be vexatious? SMV: {1}, good_karma: {2:.2f}, bad_karma: {3:.2f}".format(user.name, user.smv, user.good_karma, user.bad_karma))
  184.             else:
  185.                 log.debug("Not changing user: {0} (immutable flair {1})".format(user.name, user.current_flair))
  186.  
  187.         elif user.current_flair != "hb{0}".format(user.smv):
  188.             log.info("Updating user: {0} flair to hb{1}".format(user.name, user.smv))
  189.             tbp.flair.set(user.name, "Hβ{0}".format(user.smv), "hb{0}".format(user.smv))
  190.  
  191.             user.current_flair = "hb{0}".format(user.smv)
  192.         #
  193.         else:
  194.             log.debug("User: {0} still an Hβ{1}".format(user.name, user.smv))
  195.         #
  196.         if (user.smv > 7 or user.current_flair in GOOD_FLAIRS) and user.name not in approved_users:
  197.             log.debug("Adding approved contributor: {0}".format(user.name))
  198.             tbp.contributor.add(user.name)
  199.         elif (user.smv < 4 or user.current_flair in BAD_FLAIRS) and user.name in approved_users:
  200.             log.info("Removing approved contributor: {0}".format(user.name))
  201.             tbp.contributor.remove(user.name)
  202.     #
  203.     pickle.dump(users, open("users.pickle", "wb"))
  204.  
  205. def botloop(first_recalc=datetime.now()):
  206.     next_recalc = first_recalc
  207.     #
  208.     for comment in tbp.stream.comments():
  209.         if datetime.now() > next_recalc:
  210.             update_flairs()
  211.             next_recalc = datetime.now() + timedelta(minutes=15)
  212.             log.debug("Next refresh: {0:%Y/%m/%d %I:%M:%S%p}".format(next_recalc))
  213.         #
  214.         threat_level = comment.submission.link_flair_css_class
  215.         if threat_level == None or comment.submission.link_flair_text == None or comment.submission.link_flair_text not in TODAYS_THREAT_LEVEL.values():
  216.             if randint(0,1) == 0: # 50-50 shot of getting "Severe"
  217.                 threat_selector = 0
  218.             else:
  219.                 threat_selector = randint(1, len(TODAYS_THREAT_LEVEL) - 3) # Won't randomly assign the last two link flairs
  220.             threat_level = list(TODAYS_THREAT_LEVEL.keys())[threat_selector]
  221.             log.info("Setting threat level for submission '{0}' [id: {1}] to {2}".format(comment.submission.title, comment.submission.id, TODAYS_THREAT_LEVEL[threat_level]))
  222.             comment.submission.mod.flair(css_class=threat_level, text=TODAYS_THREAT_LEVEL[threat_level])
  223.         else:
  224.             log.debug("Threat level for submission '{0}' [id: {1}] is {2})".format(comment.submission.title, comment.submission.id, TODAYS_THREAT_LEVEL[threat_level]))
  225.         #      
  226.         if comment.author != None:
  227.             name = comment.author.name
  228.             author_rank = comment.author_flair_css_class
  229.             #
  230.             if not name in users.keys():
  231.                 users[name] = Tracker(name)
  232.                 log.info("New user: {0}".format(name))
  233.             elif datetime.now() > users[name].next_refresh:
  234.                 users[name].update()
  235.                 log.debug("User: {0} scanned {1} times".format(name, users[name].scan_count))
  236.                 users[name].last_seen = datetime.now()
  237.             else:
  238.                 log.debug("Skipping user: {0}, next refresh {1:%Y/%m/%d %I:%M:%S%p}".format(name, users[name].next_refresh))
  239.                 users[name].last_seen = datetime.now()
  240.  
  241.             if users[name].current_flair != author_rank:
  242.                 log.info("Updating tracked flair for user: {0} - was {1}, now {2}".format(name, users[name].current_flair, author_rank))
  243.                 users[name].current_flair = author_rank
  244.             #
  245.             if (author_rank == None or author_rank == "") and users[name].scan_count > 4:
  246.                 if datetime.now() > users[name].warned + timedelta(minutes=30):
  247.                     if name not in FLAIR_EXEMPT and (users[name].smv < 4 or comment.score < 1) and users[name].current_flair not in BAD_FLAIRS and comment.id not in users[name].comments_eaten:
  248.                         users[name].comments_eaten.append(comment.id)
  249.                         comment.mod.remove()
  250.                         log.info("Removed comment {0} by unflaired user: {1}".format(comment.id, name))
  251.                     else:
  252.                         log.info("Not removing comment {0} by unflaired user: {1} (exempt or too high value)".format(comment.id, name))
  253.                 else:
  254.                     reply = comment.reply("Apologies for the inconvenience, but user flairs are mandatory on /r/TheBluePill. Please toggle your user flair back on at your earliest convenience (if you've opted out of the redesign, you'll need to disable this Subreddit's custom CSS to do so.) Your comments will be automatically filtered until you've re-enabled your flair, but the filtered comments will be restored when you're properly flaired again.\n\nThanks for choosing r/TheBluePill!\n\netc. etc., -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!*")
  255.                     reply.mod.distinguish()
  256.                     users[name].warned = datetime.now()
  257.                     log.info("Warned user: {0}, flairs are mandatory.".format(name))
  258.             else:
  259.                 if bool(users[name].warned) and len(users[name].comments_eaten) > 0:
  260.                     for comment in users[name].comments_eaten:
  261.                         reddit.comment(id=comment).mod.approve()
  262.                         log.info("Restoring comment {0} for user: {1} (is wearing flair now)".format(comment, name))
  263.                     users[name].comments_eaten = []
  264.  
  265.             if author_rank in THREAT_MATRIX[threat_level] and name != comment.submission.author:
  266.                 comment.mod.remove()
  267.                 log.info("Removing comment by user: {0} ({1}) on submission '{2}' [id: {3}] ({4}), SMV too low!".format(name, author_rank, comment.submission.title, comment.id, TODAYS_THREAT_LEVEL[threat_level]))
  268.  
  269.  
  270. NEED_TO_UPDATE_OBJCLASS = False
  271. try:
  272.     users = pickle.load(open("users.pickle", "rb"))
  273.     log.info("Re-loading database")
  274.     if NEED_TO_UPDATE_OBJCLASS:
  275.         log.debug("Updating object format")
  276.         for user in users.values():
  277.             users[user.name] = Tracker(user.name, False, user.good_points, user.bad_points, user.scan_count, user.next_refresh, user.current_flair, user.warned, user.comments_eaten, user.current_shittest, user.last_seen)
  278. except IOError:
  279.     users = {}
  280.     log.info("I/O error accessing database, starting fresh")
  281.  
  282.  
  283. try:
  284.     botloop()
  285. except prawcore.PrawcoreException:
  286.     praw_fucked_up()
  287.     botloop()
  288. except KeyboardInterrupt:
  289.     log.info("VirtuteTron going off-line")
  290.     pickle.dump(users, open("users.pickle", "wb"))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement