Advertisement
CrashARuntimeToday

VirtueTron9000 v0.3.2_pre11

Jul 5th, 2018
271
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.76 KB | None | 0 0
  1. #!/usr/bin/python
  2. LICENCE = "WTFPL", "http://www.wtfpl.net/about/"
  3. VERSION = "v0.3.2_pre11"
  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.  
  36. class Tracker:
  37.     def __init__(self, name, good_karma=0, bad_karma=0, new_user=True, scan_count=0, next_refresh=None, is_approved=False, is_purged=False, current_flair=None, warned=None, comments_eaten=[], good_points=[], bad_points=[]):
  38.         self.name = name
  39.         self.smv = 0
  40.         self.scan_count = scan_count
  41.         self.next_refresh = next_refresh
  42.         self.is_approved = is_approved
  43.         self.is_purged = is_purged
  44.         self.current_flair = current_flair
  45.         self.warned = warned
  46.         self.comments_eaten = comments_eaten
  47.         if len(good_points) == 0:
  48.             self.good_points = [good_karma]
  49.         else:
  50.             self.good_points = good_points
  51.         if len(bad_points) == 0:
  52.             self.bad_points = [bad_karma]
  53.         else:
  54.             self.bad_points = bad_points
  55.         self.best_post_karma = 0
  56.         self.worst_post_karma = 0
  57.         if new_user and not good_karma == 0 and bad_karma == 0:
  58.             self.update(good_karma, bad_karma)
  59.         else:
  60.             self.good_karma = good_karma
  61.             self.bad_karma = bad_karma
  62.     #For some reason VSCode eats the indents on blank lines and this gives the python interpreter fits when I copy/paste
  63.     @property
  64.     def score(self):
  65.         return sum(self.good_points) / len(self.good_points) + sum(self.bad_points) / len(self.bad_points)
  66.     #
  67.     def update(self, good_karma, bad_karma):
  68.         self.good_karma = good_karma
  69.         self.bad_karma = bad_karma
  70.         self.good_points.insert(0, good_karma)
  71.         if len(self.good_points) > 8:
  72.             self.good_points.pop()
  73.             log.debug("Dropping oldest good karma sample for user: {0}".format(self.name))
  74.         self.bad_points.insert(0, bad_karma)
  75.         if len(self.bad_points) > 8:
  76.             self.bad_points.pop()
  77.             log.debug("Dropping oldest bad karma sample for user: {0}".format(self.name))
  78.         self.scan_count += 1
  79.         self.next_refresh = datetime.now() + timedelta(minutes=15)
  80.  
  81.  
  82. delay = 2
  83. last_fuckup = None
  84. def praw_fucked_up():
  85.     global delay
  86.     global last_fuckup
  87.     log.warning("Reddit API error: waiting {0} seconds".format(delay))
  88.     try:
  89.         if delay > 128 or datetime.now() > last_fuckup + timedelta(minutes=2): delay = 2
  90.     except TypeError:
  91.         delay = 2
  92.     sleep(delay)
  93.     delay *= 2
  94.     last_fuckup = datetime.now()
  95.  
  96.  
  97. NICE_LIST = []
  98. NAUGHTY_LIST = "theredpill", "marriedredpill", "mgtow", "braincels", "asktrp", "askmrp", "redpillwomen", "redpillwives", "cringeanarchy", "the_donald", "rpchristians", "pussypassdenied", "mensrights", "milliondollarextreme", "4chan", "whereareallthegoodmen"
  99. def calc_score(name):
  100.     good_karma, bad_karma, good_count, bad_count = 0, 0, 0, 0
  101.    
  102.     for comment in reddit.redditor(name).comments.new(limit=100):
  103.         sub = comment.subreddit.display_name.lower()
  104.         karma = comment.score
  105.         if sub == "thebluepill":
  106.             good_karma += karma - 1
  107.             good_count += 1
  108.             if comment.score - 1 > users[name].best_post_karma:
  109.                 users[name].best_post_karma = comment.score - 1
  110.             if comment.score - 1 < users[name].worst_post_karma:
  111.                 users[name].worst_post_karma = comment.score - 1
  112.         elif sub in NICE_LIST:
  113.             good_karma += (karma - 1) / 2
  114.         elif sub in NAUGHTY_LIST and karma > 1:
  115.             bad_karma -= karma - 1
  116.             bad_count += 1
  117.    
  118.     if good_count > 0:
  119.         good_karma /= good_count
  120.  
  121.     if bad_count > 0:
  122.         bad_karma /= bad_count
  123.         # Assign an immutable flair (if appropriate)
  124.         if bad_count > 10  and abs(bad_karma) > good_karma and name in users.keys() and not users[name].is_purged and not users[name].current_flair == "vexatious":
  125.             log.info("User: {0} is vexatious ({1} posts in NAUGHTY_LIST)".format(name, bad_count))
  126.             tbp.flair.set(name, "VEXATIOUS LITIGANT", "vexatious")
  127.             users[name].current_flair = "vexatious"
  128.  
  129.     log.info("Scanned user: {0}, good_karma: {1} ({2} comments), bad_karma: {3} ({4} comments)".format(name, good_karma, good_count, bad_karma, bad_count))
  130.     return good_karma, bad_karma
  131.  
  132.  
  133. IMMUTABLE_FLAIRS = "vanguard", "vexatious", "endorsedflair", "alpha", "betaasfuck", "feeemale", "purged"
  134. BAD_FLAIRS = ["purged", "vexatious"]
  135. GOOD_FLAIRS = ["endorsedflair", "vanguard", "alpha", "betaasfuck", "feeemale"]
  136.  
  137. def get_flair(name):
  138.     while True:
  139.         try:
  140.             for flair in tbp.flair(redditor=name):
  141.                 return flair["flair_css_class"]
  142.         except prawcore.PrawcoreException:
  143.             praw_fucked_up()
  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())
  151.     approved_users = list(tbp.contributor())
  152.  
  153.     for user in sorted(users.values(), key=lambda x: x.score):
  154.         #temp fix since I've got fucked up is_approved data cached <-- leaving it around in case someone manually removes a contributor
  155.         if user.is_approved and user.name not in approved_users:
  156.             log.debug("Clearing erroneous approved flag on user: {0}".format(user.name))
  157.             user.is_approved = False
  158.  
  159.         if not user.is_purged and user.name in purged_users:
  160.             if user.current_flair != "purged":
  161.                 log.info("Marking user: {0} purged".format(user.name))
  162.                 tbp.flair.set(user.name, "PURGED", "purged")
  163.                 user.current_flair = "purged"
  164.                 user.is_purged = True
  165.             else:
  166.                 log.debug("User: {0} remains purged".format(user.name))
  167.         elif user.is_purged and user.name not in purged_users:
  168.             if user.current_flair == "purged":
  169.                 log.info("User: {0} was unbanned, clearing flair")
  170.                 tbp.flair.set(user.name, "", "")
  171.  
  172.         i += 1
  173.         user.smv = ceil((i / total) * 10)
  174.         log.debug("User: {0}, SMV: {1}, score: {2} (current flair {3})".format(user.name, user.smv, user.score, user.current_flair))
  175.  
  176.         if user.current_flair == None:
  177.             # Check if user deleted their account
  178.             try:
  179.                 for x in reddit.redditor(user.name).new(limit=1):
  180.                     y = x
  181.             except (prawcore.exceptions.NotFound, prawcore.exceptions.Forbidden):
  182.                 log.info("User: {0} is deleted, dropping from userlist".format(user.name))
  183.                 del users[user.name]
  184.                 total -= 1
  185.                 continue
  186.             log.debug("No cached flair for user: {0}, checking Reddit".format(user.name))
  187.             user.current_flair = get_flair(user.name)
  188.             log.debug("Reddit says user: {0}'s flair is {1}".format(user.name, user.current_flair))
  189.  
  190.         if user.current_flair in IMMUTABLE_FLAIRS:
  191.             if user.current_flair == "vexatious" and user.smv > 2 and sum(user.good_points) > abs(sum(user.bad_points)):
  192.                 log.warn("Should user: {0} be vexatious? SMV: {1}, good_karma: {2}, bad_karma: {3}".format(user.name, user.smv, user.score, user.bad_karma))
  193.             else:
  194.                 log.debug("Not changing user: {0} (immutable flair {1})".format(user.name, user.current_flair))
  195.  
  196.         elif user.current_flair != "hb{0}".format(user.smv):
  197.             log.info("Updating user: {0} flair to hb{1}".format(user.name, user.smv))
  198.             tbp.flair.set(user.name, "Hβ{0}".format(user.smv), "hb{0}".format(user.smv))
  199.  
  200.             user.current_flair = "hb{0}".format(user.smv)
  201.  
  202.         else:
  203.             log.debug("User: {0} still an Hβ{1}".format(user.name, user.smv))
  204.  
  205.         if (user.smv > 7 or user.current_flair in GOOD_FLAIRS) and not user.is_approved:
  206.             if user.name not in approved_users:
  207.                 log.info("Theoretically adding approved contributor: {0} (fuckin' rate limits)".format(user.name))
  208.                 #tbp.contributor.add(user.name)
  209.                 #user.is_approved = True
  210.                 #sleep(2) # Transitional, I've got so many people that should be approved but my data was fucked
  211.             elif user.name in approved_users:
  212.                 user.is_approved = True
  213.                 log.debug("Marking user: {0} approved".format(user.name))
  214.             elif user.smv < 4 and user in approved_users:
  215.                 log.info("Removing approved contributor: {0}".format(user.name))
  216.                 tbp.contributor.remove(user.name)
  217.                 user.is_approved = False
  218.  
  219.     pickle.dump(users, open("users.pickle", "wb"))
  220.  
  221.  
  222. TODAYS_THREAT_LEVEL = {"tlsevere":"Severe", "tlhigh":"High", "tlelevated":"Elevated", "tlguarded":"Guarded", "tllow":"Low"}
  223. THREAT_MATRIX = {"tllow": BAD_FLAIRS + ["hb{0}".format(x) for x in range(1,7)],
  224.                  "tlguarded": BAD_FLAIRS + ["hb{0}".format(x) for x in range(1,5)],
  225.                  "tlelevated": BAD_FLAIRS + ["hb{0}".format(x) for x in range(1,3)],
  226.                  "tlhigh": BAD_FLAIRS,
  227.                  "tlsevere": []}
  228. FLAIR_EXEMPT = "sofcknwrong"
  229. def botloop(first_recalc):
  230.     next_recalc = first_recalc
  231.    
  232.     for comment in tbp.stream.comments():
  233.         if datetime.now() > next_recalc:
  234.             update_flairs()
  235.             next_recalc = datetime.now() + timedelta(minutes=15)
  236.             log.debug("Next refresh: {0}".format(next_recalc))
  237.  
  238.         threat_level = comment.submission.link_flair_css_class
  239.         if threat_level == None or comment.submission.link_flair_text == None or comment.submission.link_flair_text not in TODAYS_THREAT_LEVEL.values():
  240.             threat_selector = randint(0, len(TODAYS_THREAT_LEVEL) - 3) # Won't randomly assign the last two link flairs
  241.             if randint(0,1) == 0:
  242.                 threat_selector = 0 # Biasing random threat selection towards "Severe"
  243.             threat_level = list(TODAYS_THREAT_LEVEL.keys())[threat_selector]
  244.             log.info("Setting threat level for submission '{0}' [id: {1}] to {2}".format(comment.submission.title, comment.submission.id, TODAYS_THREAT_LEVEL[threat_level]))
  245.             comment.submission.mod.flair(css_class=threat_level, text=TODAYS_THREAT_LEVEL[threat_level])
  246.         else:
  247.             log.debug("Threat level for submission '{0}' [id: {1}] is {2})".format(comment.submission.title, comment.submission.id, TODAYS_THREAT_LEVEL[threat_level]))
  248.                
  249.         if comment.author != None:
  250.             name = comment.author.name
  251.             author_rank = comment.author_flair_css_class
  252.  
  253.             if not name in users.keys():
  254.                 users[name] = Tracker(name)
  255.                 good_karma, bad_karma = calc_score(name)
  256.                 users[name].update(good_karma, bad_karma)
  257.                 log.info("New user: {0}".format(name))
  258.             elif datetime.now() > users[name].next_refresh:
  259.                 good_karma, bad_karma = calc_score(name)
  260.                 users[name].update(good_karma, bad_karma)
  261.                 log.debug("User: {0} scanned {1} times".format(name, users[name].scan_count))
  262.             else:
  263.                 log.debug("Skipping user: {0}, next refresh {1}".format(name, users[name].next_refresh))
  264.  
  265.             if users[name].current_flair != author_rank:
  266.                 log.info("Updating tracked flair for user: {0} - was {1}, now {2}".format(name, users[name].current_flair, author_rank))
  267.                 users[name].current_flair = author_rank
  268.            
  269.             if (author_rank == None or author_rank == "") and users[name].scan_count > 4:
  270.                 if datetime.now() > users[name].warned + timedelta(hours=1):
  271.                     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:
  272.                         users[name].comments_eaten.append(comment.id)
  273.                         comment.mod.remove()
  274.                         log.info("Removed comment {0} by unflaired user: {1}".format(comment.id, name))
  275.                     else:
  276.                         log.info("Not removing comment {0} by unflaired user: {1} (exempt or too high value)".format(comment.id, name))
  277.                 else:
  278.                     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!*")
  279.                     reply.mod.distinguish()
  280.                     users[name].warned = datetime.now()
  281.                     log.info("Warned user: {0}, flairs are mandatory.".format(name))
  282.             else:
  283.                 if bool(users[name].warned) and len(users[name].comments_eaten) > 0:
  284.                     for comment in users[name].comments_eaten:
  285.                         reddit.comment(id=comment).mod.approve()
  286.                         log.info("Restoring comment {0} for user: {1} (is wearing flair now)".format(comment, name))
  287.                     users[name].comments_eaten = []
  288.  
  289.             if author_rank in THREAT_MATRIX[threat_level] and name != comment.submission.author:
  290.                 comment.mod.remove()
  291.                 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]))
  292.  
  293.  
  294. NEED_TO_UPDATE_OBJCLASS = False
  295. try:
  296.     users = pickle.load(open("users.pickle", "rb"))
  297.     log.info("Re-loading database")
  298.     if NEED_TO_UPDATE_OBJCLASS:
  299.         log.debug("Updating object format")
  300.         for user in users.values():
  301.             users[user.name] = Tracker(user.name, user.good_karma, user.bad_karma, False, user.scan_count, user.next_refresh, user.is_approved, user.is_purged, user.current_flair, user.warned, user.comments_eaten, user.good_points, user.bad_points)
  302. except IOError:
  303.     users = {}
  304.     log.info("I/O error accessing database, starting fresh")
  305.  
  306.  
  307. try:
  308.     botloop(datetime.now())# + timedelta(minutes=5))
  309. except prawcore.PrawcoreException:
  310.     praw_fucked_up()
  311.     botloop(datetime.now())# + timedelta(minutes=5))
  312. except KeyboardInterrupt:
  313.     log.info("VirtuteTron going off-line")
  314.     pickle.dump(users, open("users.pickle", "wb"))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement