submissions.py
1 import traceback 2 from multiprocessing import Lock 3 4 from praw import Reddit 5 6 from blottertrax.config import Config 7 from blottertrax.database import Database 8 from blottertrax.description_provider import DescriptionProvider 9 from blottertrax.exceptions.description_exception import DescriptionException 10 from blottertrax.helper import templates 11 from blottertrax.helper.excluded_artists import ExcludedArtists 12 from blottertrax.helper.self_promo_detector import SelfPromoDetector 13 from blottertrax.helper.title_parser import TitleParser 14 from blottertrax.logger import Logger 15 from blottertrax.services.lastfm import LastFM 16 from blottertrax.services.overall_threshold_service import OverallThresholdService 17 from blottertrax.services.soundcloud import Soundcloud 18 from blottertrax.services.youtube import Youtube 19 20 21 class Submissions: 22 threshold_services: list = [] 23 24 def __init__(self, lock: Lock): 25 try: 26 self.config = Config() 27 self.logger = Logger() 28 self.database = Database(lock) 29 self.description_provider = DescriptionProvider() 30 self.threshold_services = [Youtube(), Soundcloud(), LastFM()] 31 32 self.reddit = Reddit(client_id=self.config.REDDIT.CLIENT_ID, client_secret=self.config.REDDIT.CLIENT_SECRET, 33 password=self.config.REDDIT.PASSWORD, user_agent=self.config.REDDIT.USER_AGENT, 34 username=self.config.REDDIT.USER_NAME) 35 36 except KeyError: 37 self.logger.exception('Check if the configuration is set right') 38 exit() 39 40 def run(self): 41 for submission in self._get_submissions(): 42 parsed_submission = TitleParser.create_parsed_submission_from_submission(submission) 43 44 if ExcludedArtists.is_excluded(parsed_submission): 45 continue 46 47 if SelfPromoDetector.is_self_promo(parsed_submission, submission): 48 reply = templates.self_promotion.format(submission.author.name) 49 submission.mod.remove(False, mod_note="Self promotion") 50 self._reply_with_sticky_post(submission, reply) 51 continue 52 53 exceeds_threshold = self._do_service_checks(parsed_submission, submission) 54 55 try: 56 if exceeds_threshold is False and parsed_submission.success is True: 57 # Yeey this post probably isn't breaking the rules 🌈 58 self._reply_with_sticky_post(submission, self.description_provider.get_reply(parsed_submission)) 59 except DescriptionException: 60 # Can't find recording on Musicbrainz, skipping 61 self.logger.error(f"Can't find musicbrainz info for {submission.permalink}") 62 pass 63 64 def _do_service_checks(self, parsed_submission, submission) -> bool: 65 """ 66 Loop through all services and check them to determine if we need to remove the post for exceeding thresholds. 67 """ 68 exceeds_threshold = False 69 overall_threshold_service = OverallThresholdService() 70 71 for service in self.threshold_services: 72 try: 73 # Some services can run without a successful parsed submission and just need the url 74 # If this is not the case, lets skip it now. 75 if service.requires_fully_parsed_submission() and not parsed_submission.success: 76 continue 77 78 result = service.get_service_result(parsed_submission) 79 80 if result.exceeds_threshold is True: 81 self._perform_exceeds_threshold_mod_action(submission, result) 82 exceeds_threshold = True 83 break 84 else: 85 overall_threshold_service.add_service_result(result) 86 87 except Exception: 88 # Go ahead and continue execution, don't want to fail completely just because one service failed. 89 self.logger.exception(f"Getting {type(service).__name__} failed") 90 self.database.log_error_causing_submission(parsed_submission, submission, traceback.format_exc()) 91 92 # TODO: Enable this when new rules roll out. 93 # # If none of the other checkers exceeded the threshold, check all the combined listeners. 94 # if not exceeds_threshold: 95 # result = overall_threshold_service.get_service_result() 96 # if result.exceeds_threshold: 97 # self._perform_exceeds_threshold_mod_action(submission, result) 98 99 return exceeds_threshold 100 101 def _get_submissions(self): 102 for submission in self.reddit.subreddit(self.config.REDDIT.SUBREDDIT).stream.submissions(): 103 if submission is None: 104 return None 105 106 if self.database.known_submission(submission) is True: 107 continue 108 109 self.logger.info(f"Handling {submission.title} - http://reddit.com{submission.permalink}") 110 111 # Always save submissions, in case of errors log it and continue 112 self.database.save_submission(submission) 113 114 yield submission 115 116 def _perform_exceeds_threshold_mod_action(self, submission, service): 117 if self.config.REDDIT.REMOVE_SUBMISSIONS_ON_ERROR is True: 118 self._remove_submission_exceeding_threshold(submission, service) 119 else: 120 submission.report(templates.mod_note_exceeding_threshold.format(service.service_name, service.threshold, 121 service.listeners_count)) 122 123 def _remove_submission_exceeding_threshold(self, submission, service): 124 reply = templates.submission_exceeding_threshold.format( 125 submission.author.name, service.service_name, service.threshold, service.listeners_count, submission.id 126 ) 127 # This is *theoretically* supposed to add a modnote to the removal reason so mods know why. 128 # Currently not working? 129 mod_message = templates.mod_note_exceeding_threshold.format(service.service_name, service.threshold, 130 service.listeners_count) 131 submission.mod.remove(False, mod_note=mod_message) 132 self._reply_with_sticky_post(submission, reply) 133 134 @staticmethod 135 def _reply_with_sticky_post(submission, reply_text): 136 comment = submission.reply(reply_text) 137 comment.mod.distinguish("yes", sticky=True)