Beispiel #1
0
    def _reply_to_comment(self, response: SummonsResponse) -> SummonsResponse:
        log.debug('Sending response to summons comment %s. MESSAGE: %s',
                  response.summons.comment_id, response.message)
        try:
            reply_comment = self.response_handler.reply_to_comment(
                response.summons.comment_id, response.message)
            response.comment_reply_id = reply_comment.id
        except APIException as e:
            if e.error_type == 'DELETED_COMMENT':
                log.debug('Comment %s has been deleted',
                          response.summons.comment_id)
                response.message = 'DELETED COMMENT'
            elif e.error_type == 'THREAD_LOCKED':
                log.info('Comment %s is in a locked thread',
                         response.summons.comment_id)
                response.message = 'THREAD LOCKED'
            elif e.error_type == 'TOO_OLD':
                log.info('Comment %s is too old to reply to',
                         response.summons.comment_id)
                response.message = 'TOO OLD'
            elif e.error_type == 'RATELIMIT':
                log.exception('PRAW Ratelimit exception', exc_info=False)
                raise
            else:
                log.exception('APIException without error_type', exc_info=True)
                raise
        except Exception:
            log.exception('Problem leaving response', exc_info=True)
            raise

        return response
Beispiel #2
0
    def process_link_repost_request(self,
                                    summons: Summons,
                                    post: Post,
                                    monitored_sub: MonitoredSub = None):
        response = SummonsResponse(summons=summons)

        search_results = get_link_reposts(post.url,
                                          self.uowm,
                                          get_default_link_search_settings(
                                              self.config),
                                          post=post,
                                          get_total=True)
        search_results = filter_search_results(
            search_results,
            reddit=self.reddit.reddit,
            uitl_api=f'{self.config.util_api}/maintenance/removed')

        if not monitored_sub:
            response.message = self.response_builder.build_default_comment(
                search_results, signature=False)
        else:
            response.message = self.response_builder.build_sub_comment(
                monitored_sub, search_results, signature=False)

        if search_results.matches:
            save_link_repost(post, search_results.matches[0].post, self.uowm,
                             'summons')

        self._send_response(response)
Beispiel #3
0
 def _send_ban_notification(self, summons: Summons) -> NoReturn:
     response = SummonsResponse(summons=summons)
     response.status = 'success'
     response.message = OVER_LIMIT_BAN.format(
         ban_expires=datetime.utcnow() + timedelta(hours=1))
     redditor = self.reddit.redditor(summons.requestor)
     self.response_handler.send_private_message(
         redditor, response.message, subject='Temporary RepostSleuth Ban')
     self._save_response(response)
def process_summons(self, s):
    with self.uowm.start() as uow:
        log.info('Starting summons %s on sub %s', s.id, s.subreddit)
        updated_summons = uow.summons.get_by_id(s.id)
        if updated_summons and updated_summons.summons_replied_at:
            log.info('Summons %s already replied, skipping', s.id)
            return
        try:
            with self.redlock.create_lock(f'summons_{s.id}', ttl=120000):
                post = uow.posts.get_by_post_id(s.post_id)
                if not post:
                    post = self.summons_handler.save_unknown_post(s.post_id)

                if not post:
                    response = SummonsResponse(summons=s)
                    response.message = 'Sorry, I\'m having trouble with this post. Please try again later'
                    log.info(
                        'Failed to ingest post %s.  Sending error response',
                        s.post_id)
                    self.summons_handler._send_response(response)
                    return

                try:
                    self.summons_handler.process_summons(s, post)
                except ResponseException as e:
                    if e.response.status_code == 429:
                        log.error('IP Rate limit hit.  Waiting')
                        time.sleep(60)
                        return
                except AssertionError as e:
                    if 'code: 429' in str(e):
                        log.error('Too many requests from IP.  Waiting')
                        time.sleep(60)
                        return
                except APIException as e:
                    if hasattr(e,
                               'error_type') and e.error_type == 'RATELIMIT':
                        log.error(
                            'Hit API rate limit for summons %s on sub %s.',
                            s.id, s.subreddit)
                        return
                        #time.sleep(60)

                # TODO - This sends completed summons events to influx even if they fail
                summons_event = SummonsEvent(float(
                    (datetime.utcnow() - s.summons_received_at).seconds),
                                             s.summons_received_at,
                                             s.requestor,
                                             event_type='summons')
                self.summons_handler._send_event(summons_event)
                log.info('Finished summons %s', s.id)
        except RedLockError:
            log.error('Summons %s already in process', s.id)
            time.sleep(3)
Beispiel #5
0
    def _send_response(self, response: SummonsResponse) -> NoReturn:
        """
        Take a response object and send a response to the summons.  If we're banned on the sub send a PM instead
        :param response: SummonsResponse Object
        """

        with self.uowm.start() as uow:
            banned = uow.banned_subreddit.get_by_subreddit(
                response.summons.subreddit)
        if banned or (self.config.summons_send_pm_subs
                      and response.summons.subreddit
                      in self.config.summons_send_pm_subs):
            try:
                self._send_private_message(response)
            except APIException as e:
                if e.error_type == 'NOT_WHITELISTED_BY_USER_MESSAGE':
                    response.message = 'NOT_WHITELISTED_BY_USER_MESSAGE'
        else:
            try:
                self._reply_to_comment(response)
            except Forbidden:
                log.info('Banned on %s, sending PM',
                         response.summons.subreddit)
                self._send_private_message(response)
        self._save_response(response)
Beispiel #6
0
 def _send_already_responded_msg(self, summons: Summons,
                                 perma_link: Text) -> NoReturn:
     response = SummonsResponse(summons=summons)
     response.status = 'success'
     response.message = SUMMONS_ALREADY_RESPONDED.format(
         perma_link=perma_link)
     redditor = self.reddit.redditor(summons.requestor)
     try:
         self.response_handler.send_private_message(
             redditor,
             response.message,
             post_id=summons.post_id,
             comment_id=summons.comment_id,
             source='summons')
     except APIException as e:
         if e.error_type == 'NOT_WHITELISTED_BY_USER_MESSAGE':
             response.message = 'NOT_WHITELISTED_BY_USER_MESSAGE'
     self._save_response(response)
Beispiel #7
0
    def _enable_watch(self, summons: Summons) -> NoReturn:

        response = SummonsResponse(summons=summons)
        with self.uowm.start() as uow:
            existing_watch = uow.repostwatch.find_existing_watch(
                summons.requestor, summons.post_id)
            if existing_watch:
                if not existing_watch.enabled:
                    log.info(
                        'Found existing watch that is disabled.  Enabling watch %s',
                        existing_watch.id)
                    existing_watch.enabled = True
                    response.message = WATCH_ENABLED
                    uow.commit()
                    self._send_response(response)
                    return
                else:
                    response.message = WATCH_ALREADY_ENABLED
                    self._send_response(response)
                    return

        repost_watch = RepostWatch(post_id=summons.post_id,
                                   user=summons.requestor,
                                   enabled=True)

        with self.uowm.start() as uow:
            uow.repostwatch.add(repost_watch)
            try:
                uow.commit()
                response.message = WATCH_ENABLED
            except Exception as e:
                log.exception('Failed save repost watch', exc_info=True)
                response.message = 'An error prevented me from creating a watch on this post.  Please try again'

        self._send_response(response)
Beispiel #8
0
 def _disable_watch(self, summons: Summons) -> NoReturn:
     response = SummonsResponse(summons=summons)
     with self.uowm.start() as uow:
         existing_watch = uow.repostwatch.find_existing_watch(
             summons.requestor, summons.post_id)
         if not existing_watch or (existing_watch
                                   and not existing_watch.enabled):
             response.message = WATCH_DISABLED_NOT_FOUND
             self._send_response(response)
             return
         existing_watch.enabled = False
         try:
             uow.commit()
             response.message = WATCH_DISABLED
             log.info('Disabled watch for post %s for user %s',
                      summons.post_id, summons.requestor)
         except Exception as e:
             log.exception('Failed to disable watch %s',
                           existing_watch.id,
                           exc_info=True)
             response.message = 'An error prevented me from removing your watch on this post.  Please try again'
         self._send_response(response)
Beispiel #9
0
    def process_image_repost_request(self,
                                     summons: Summons,
                                     post: Post,
                                     monitored_sub: MonitoredSub = None):

        response = SummonsResponse(summons=summons)
        search_settings = get_default_image_search_settings(self.config)
        target_image_match, target_meme_match, target_annoy_distance = self._get_target_distances(
            monitored_sub)
        search_settings.target_match_percent = target_image_match
        search_settings.target_meme_match_percent = target_meme_match

        try:
            search_results = self.image_service.check_image(
                post.url,
                post=post,
                search_settings=search_settings,
                source='summons')
        except NoIndexException:
            log.error(
                'No available index for image repost check.  Trying again later'
            )
            time.sleep(10)
            return

        if monitored_sub:
            response.message = self.response_builder.build_sub_comment(
                monitored_sub, search_results, signature=False)
        else:
            response.message = self.response_builder.build_default_comment(
                search_results, signature=False)

        if search_results.matches:
            save_image_repost_result(search_results,
                                     self.uowm,
                                     source='summons')

        self._send_response(response)
Beispiel #10
0
    def handle_summons(self):
        """
        Continually check the summons table for new requests.  Handle them as they are found
        """
        while True:
            try:
                with self.uowm.start() as uow:
                    summons = uow.summons.get_unreplied()
                    for s in summons:
                        log.info('Starting summons %s', s.id)
                        post = uow.posts.get_by_post_id(s.post_id)
                        if not post:
                            post = self.save_unknown_post(s.post_id)

                        if not post:
                            response = SummonsResponse(summons=summons)
                            response.message = 'Sorry, I\'m having trouble with this post. Please try again later'
                            log.info(
                                'Failed to ingest post %s.  Sending error response',
                                s.post_id)
                            self._send_response(response)
                            continue

                        self.process_summons(s, post)
                        # TODO - This sends completed summons events to influx even if they fail
                        summons_event = SummonsEvent(
                            (datetime.utcnow() -
                             s.summons_received_at).seconds,
                            s.summons_received_at,
                            s.requestor,
                            event_type='summons')
                        self._send_event(summons_event)
                        log.info('Finished summons %s', s.id)
                time.sleep(2)
            except Exception:
                log.exception('Exception in handle summons thread',
                              exc_info=True)
Beispiel #11
0
 def _send_private_message(self,
                           response: SummonsResponse) -> SummonsResponse:
     redditor = self.reddit.redditor(response.summons.requestor)
     log.info('Sending private message to %s for summons in sub %s',
              response.summons.requestor, response.summons.subreddit)
     msg = BANNED_SUB_MSG.format(post_id=response.summons.post_id,
                                 subreddit=response.summons.subreddit)
     msg = msg + response.message
     self.response_handler.send_private_message(
         redditor,
         msg,
         post_id=response.summons.post_id,
         comment_id=response.summons.comment_id,
         source='summons')
     response.message = msg
     return response
Beispiel #12
0
 def _send_summons_disable_msg(self, summons: Summons):
     response = SummonsResponse(summons=summons)
     log.info('Sending summons disabled message')
     response.message = 'I\'m currently down for maintenance, check back in an hour'
     self._send_response(response)
     return
Beispiel #13
0
 def _send_unsupported_msg(self, summons: Summons, post_type: Text):
     response = SummonsResponse(summons=summons)
     response.status = 'error'
     response.message = UNSUPPORTED_POST_TYPE.format(post_type=post_type)
     self._send_response(response)
Beispiel #14
0
    def process_summons(self, summons: Summons, post: Post):
        if self.summons_disabled:
            self._send_summons_disable_msg(summons)

        if post.post_type is None or post.post_type not in self.config.supported_post_types:
            log.error('Post %s: Type %s not support',
                      f'https://redd.it/{post.post_id}', post.post_type)
            self._send_unsupported_msg(summons, post.post_type)
            return

        if self._is_banned(summons.requestor):
            log.info('User %s is currently banned', summons.requestor)
            response = SummonsResponse(summons=summons, message='USER BANNED')
            self._save_response(response)
            return

        if self._has_user_exceeded_limit(summons.requestor):
            self._ban_user(summons.requestor)
            self._send_ban_notification(summons)
            return

        with self.uowm.start() as uow:
            monitored_sub = uow.monitored_sub.get_by_sub(summons.subreddit)

            if monitored_sub:
                if monitored_sub.disable_summons_after_auto_response:
                    log.info('Sub %s has summons disabled after auto response',
                             summons.subreddit)
                    auto_response = uow.bot_comment.get_by_post_id_and_type(
                        summons.post_id, 'submonitor')
                    if auto_response:
                        self._send_already_responded_msg(
                            summons,
                            f'https://reddit.com{auto_response.perma_link}')
                        if monitored_sub.remove_additional_summons:
                            self._delete_mention(summons.comment_id)
                        return

                if monitored_sub.only_allow_one_summons and summons.requestor != 'barrycarey':
                    response = uow.bot_comment.get_by_post_id_and_type(
                        summons.post_id, 'summons')
                    if response:
                        log.info(
                            'Sub %s only allows one summons.  Existing response found at %s',
                            summons.subreddit, response.perma_link)
                        self._send_already_responded_msg(
                            summons,
                            f'https://reddit.com{response.perma_link}')
                        if monitored_sub.remove_additional_summons:
                            self._delete_mention(summons.comment_id)
                        return

        stripped_comment = self._strip_summons_flags(summons.comment_body)
        try:
            if not stripped_comment:
                base_command = 'repost'
            else:
                base_command = self.command_parser.parse_root_command(
                    stripped_comment)
        except InvalidCommandException:
            log.error('Invalid command in summons: %s', summons.comment_body)
            base_command = 'repost'

        if base_command == 'watch':
            self._enable_watch(summons)
            return
        elif base_command == 'unwatch':
            self._disable_watch(summons)
            return
        else:
            self.process_repost_request(summons,
                                        post,
                                        monitored_sub=monitored_sub)
            return