Beispiel #1
0
    def get_closest_media(
        self,
        tweet,
        log_index: Optional[str] = None
    ) -> Optional[Tuple[TweetCache, TweetCache, List[str]]]:
        """
        Attempt to get the closest media element associated with this tweet and handle any errors if they occur
        Args:
            tweet: tweepy.models.Status
            log_index (Optional[str]): Index to use for system logs. Defaults to SYSTEM

        Returns:
            Optional[List]
        """
        log_index = log_index or 'SYSTEM'

        try:
            original_cache, media_cache, media = self.twitter.get_closest_media(
                tweet)
        except tweepy.error.TweepError as error:
            # Error 136 means we are blocked
            if error.api_code == 136:
                # noinspection PyBroadException
                try:
                    api.update_status(
                        f"@{tweet.author.screen_name} Sorry, it looks like the author of this post has blocked us. For more information, please refer to:\nhttps://github.com/FujiMakoto/twitter-saucenao/#blocked-by",
                        in_reply_to_status_id=tweet.id,
                        auto_populate_reply_metadata=True)
                except Exception as error:
                    self.log.exception(
                        f"[{log_index}] An exception occurred while trying to inform a user that an account has blocked us"
                    )
                raise TwSauceNoMediaException
            # We attempted to process a tweet from a user that has restricted access to their account
            elif error.api_code in [179, 385]:
                self.log.info(
                    f"[{log_index}] Skipping a tweet we don't have permission to view"
                )
                raise TwSauceNoMediaException
            # Someone got impatient and deleted a tweet before we could get too it
            elif error.api_code == 144:
                self.log.info(
                    f"[{log_index}] Skipping a tweet that no longer exists")
                raise TwSauceNoMediaException
            # Something unfamiliar happened, log an error for later review
            else:
                self.log.error(
                    f"[{log_index}] Skipping due to unknown Twitter error: {error.api_code} - {error.reason}"
                )
                raise TwSauceNoMediaException

        # Still here? Yay! We have something then.
        return original_cache, media_cache, media
Beispiel #2
0
                def _retry(_lines):
                    _lines = self._shorten_reply(_lines)
                    try:
                        _msg = ''.join(map(str, _lines))
                        return api.update_status(_msg, **kwargs)
                    except tweepy.TweepError as error:
                        if error.api_code != 186:
                            raise error

                        return False
Beispiel #3
0
    def _post(self,
              msg: str,
              to: Optional[int],
              media_ids: Optional[List[int]] = None,
              sensitive: bool = False):
        """
        Perform a twitter API status update
        Args:
            msg (str): Message to send
            to (Optional[int]): Status ID we are replying to
            media_ids (Optional[List[int]]): List of media ID's
            sensitive (bool): Whether or not this tweet contains NSFW media

        Returns:

        """
        kwargs = {'possibly_sensitive': sensitive}

        if to:
            kwargs['in_reply_to_status_id'] = to
            kwargs['auto_populate_reply_metadata'] = True

        if media_ids:
            kwargs['media_ids'] = media_ids

        try:
            return api.update_status(msg, **kwargs)
        except tweepy.error.TweepError as error:
            if error.api_code == 136:
                self.log.warning(
                    "A user requested our presence, then blocked us before we could respond. Wow."
                )
            # We attempted to process a tweet from a user that has restricted access to their account
            elif error.api_code in [179, 385]:
                self.log.info(
                    f"Attempted to reply to a deleted tweet or a tweet we don't have permission to view"
                )
                raise TwSauceNoMediaException
            # Someone got impatient and deleted a tweet before we could get too it
            elif error.api_code == 144:
                self.log.info(f"Not replying to a tweet that no longer exists")
                raise TwSauceNoMediaException
            # Video was too short. Can happen if we're using natural previews. Repost without the video clip
            elif error.api_code == 324:
                self.log.info(
                    f"Video preview for was too short to upload to Twitter")
                return self._post(msg=msg, to=to, sensitive=sensitive)
            # Something unfamiliar happened, log an error for later review
            else:
                self.log.error(
                    f"Unable to post due to an unknown Twitter error: {error.api_code} - {error.reason}"
                )
Beispiel #4
0
    def _post(self,
              msg: typing.Union[str, typing.List[ReplyLine]],
              to: typing.Optional[int],
              media_ids: typing.Optional[typing.List[int]] = None,
              sensitive: bool = False):
        """
        Perform a twitter API status update
        Args:
            msg (Union[str, List[ReplyLine]]): Message to send
            to (typing.Optional[int]): Status ID we are replying to
            media_ids (typing.Optional[List[int]]): List of media ID's
            sensitive (bool): Whether or not this tweet contains NSFW media

        Returns:

        """
        kwargs = {'possibly_sensitive': sensitive}

        if to:
            kwargs['in_reply_to_status_id'] = to
            kwargs['auto_populate_reply_metadata'] = True

        if media_ids:
            kwargs['media_ids'] = media_ids

        lines = msg if isinstance(msg, list) else None
        if lines:
            msg = ''.join(map(str, lines))

        try:
            return api.update_status(msg, **kwargs)
        except tweepy.error.TweepError as error:
            if error.api_code == 136:
                self.log.warning(
                    "A user requested our presence, then blocked us before we could respond. Wow."
                )
            # We attempted to process a tweet from a user that has restricted access to their account
            elif error.api_code in [179, 385]:
                self.log.info(
                    f"Attempted to reply to a deleted tweet or a tweet we don't have permission to view"
                )
                raise TwSauceNoMediaException
            # Someone got impatient and deleted a tweet before we could get too it
            elif error.api_code == 144:
                self.log.info(f"Not replying to a tweet that no longer exists")
                raise TwSauceNoMediaException
            # Video was too short. Can happen if we're using natural previews. Repost without the video clip
            elif error.api_code == 324:
                self.log.info(
                    f"Video preview for was too short to upload to Twitter")
                return self._post(msg=msg, to=to, sensitive=sensitive)
            # Something unfamiliar happened, log an error for later review
            elif error.api_code == 186 and lines:
                self.log.debug("Post is too long; scrubbing message length")

                def _retry(_lines):
                    _lines = self._shorten_reply(_lines)
                    try:
                        _msg = ''.join(map(str, _lines))
                        return api.update_status(_msg, **kwargs)
                    except tweepy.TweepError as error:
                        if error.api_code != 186:
                            raise error

                        return False

                # Shorten the post as much as we can until it fits
                while True:
                    try:
                        success = _retry(lines)
                    except IndexError:
                        self.log.warning(
                            f"Failed to shorten response message to tweet {to} enough"
                        )
                        break

                    if not success:
                        self.log.debug(
                            f"Tweet to {to} still not short enough; running another pass"
                        )
                        continue

                    self.log.debug(f"Tweet for {to} shortened successfully")
                    break
            else:
                self.log.error(
                    f"Unable to post due to an unknown Twitter error: {error.api_code} - {error.reason}"
                )
Beispiel #5
0
    def send_reply(self,
                   tweet_cache: TweetCache,
                   media_cache: TweetCache,
                   sauce_cache: TweetSauceCache,
                   tracemoe_sauce: Optional[dict] = None,
                   requested: bool = True,
                   blocked: bool = False) -> None:
        """
        Return the source of the image
        Args:
            tweet_cache (TweetCache): The tweet to reply to
            media_cache (TweetCache): The tweet containing media elements
            sauce_cache (Optional[GenericSource]): The sauce found (or None if nothing was found)
            tracemoe_sauce (Optional[dict]): Tracemoe sauce query, if enabled
            requested (bool): True if the lookup was requested, or False if this is a monitored user account
            blocked (bool): If True, the account posting this has blocked the SauceBot

        Returns:
            None
        """
        tweet = tweet_cache.tweet
        sauce = sauce_cache.sauce

        if sauce is None:
            if requested:
                media = TweetManager.extract_media(media_cache.tweet)
                if not media:
                    return

                yandex_url = f"https://yandex.com/images/search?url={media[sauce_cache.index_no]}&rpt=imageview"
                tinyeye_url = f"https://www.tineye.com/search?url={media[sauce_cache.index_no]}"
                google_url = f"https://www.google.com/searchbyimage?image_url={media[sauce_cache.index_no]}&safe=off"

                api.update_status(
                    f"@{tweet.author.screen_name} Sorry, I couldn't find anything (●´ω`●)ゞ\nYour image may be cropped too much, or the artist may simply not exist in any of SauceNao's databases.\n\nTry checking one of these search engines!\n{yandex_url}\n{google_url}\n{tinyeye_url}",
                    in_reply_to_status_id=tweet.id)
            return

        # For limiting the length of the title/author
        repr = reprlib.Repr()
        repr.maxstring = 32

        # Add additional sauce URL's from trace.moe if available
        sauce_urls = []
        if tracemoe_sauce:
            if self._anime_link in ['anilist', 'animal', 'all']:
                sauce_urls.append(
                    f"https://anilist.co/anime/{tracemoe_sauce['anilist_id']}/"
                )

            if self._anime_link in ['myanimelist', 'animal', 'all'
                                    ] and tracemoe_sauce.get('mal_id'):
                sauce_urls.append(
                    f"https://myanimelist.net/anime/{tracemoe_sauce['mal_id']}/"
                )

            if self._anime_link in ['anidb', 'all']:
                sauce_urls.append(sauce.url)

        # H-Misc doesn't have a source to link to, so we need to try and provide the full title
        if sauce.index not in ['H-Misc', 'E-Hentai']:
            title = repr.repr(sauce.title).strip("'")
        else:
            repr.maxstring = 128
            title = repr.repr(sauce.title).strip("'")

        # Format the similarity string
        similarity = f'𝗔𝗰𝗰𝘂𝗿𝗮𝗰𝘆: {sauce.similarity}% ( '
        if sauce.similarity >= 85.0:
            similarity = similarity + '🔵 High )'
        elif sauce.similarity >= 70.0:
            similarity = similarity + '🟡 Medium )'
        else:
            similarity = similarity + '🟠 Low )'

        if requested:
            reply = f"@{tweet.author.screen_name} I found this in the {sauce.index} database!\n"
        else:
            reply = f"Need the sauce? I found it in the {sauce.index} database!\n"

        # If it's a Pixiv source, try and get their Twitter handle (this is considered most important and displayed first)
        twitter_sauce = None
        if isinstance(sauce, PixivSource):
            twitter_sauce = self.pixiv.get_author_twitter(
                sauce.data['member_id'])
            if twitter_sauce:
                reply += f"\n𝗔𝗿𝘁𝗶𝘀𝘁𝘀 𝗧𝘄𝗶𝘁𝘁𝗲𝗿: {twitter_sauce}"

        # Print the author name if available
        if sauce.author_name:
            author = repr.repr(sauce.author_name).strip("'")
            reply += f"\n𝗔𝘂𝘁𝗵𝗼𝗿: {author}"

        # Omit the title for Pixiv results since it's usually always non-romanized Japanese and not very helpful
        if not isinstance(sauce, PixivSource):
            reply += f"\n𝗧𝗶𝘁𝗹𝗲: {title}"

        # Add the episode number and timestamp for video sources
        if isinstance(sauce, VideoSource):
            if sauce.episode:
                reply += f"\n𝗘𝗽𝗶𝘀𝗼𝗱𝗲: {sauce.episode}"
            if sauce.timestamp:
                reply += f" ( ⏱️ {sauce.timestamp} )"

        # Add the chapter for manga sources
        if isinstance(sauce, MangaSource):
            if sauce.chapter:
                reply += f"\n𝗖𝗵𝗮𝗽𝘁𝗲𝗿: {sauce.chapter}"

        # Display our confidence rating
        reply += f"\n{similarity}"

        # Source URL's are not available in some indexes
        if sauce_urls:
            reply += "\n\n"
            reply += "\n".join(sauce_urls)
        elif sauce.source_url:
            reply += f"\n\n{sauce.source_url}"

        # Some Booru posts have bad source links cited, so we should always provide a Booru link with the source URL
        if isinstance(sauce, BooruSource) and sauce.source_url != sauce.url:
            reply += f"\n{sauce.url}"

        # Try and append bot instructions with monitored posts. This might make our post too long, though.
        if not requested:
            _reply = reply
            reply += f"\n\nNeed sauce elsewhere? Just follow and (@)mention me in a reply and I'll be right over!"

        try:
            if tracemoe_sauce and (not tracemoe_sauce['is_adult']
                                   or self._nsfw_previews):
                tw_response = self.twython.upload_video(media=io.BytesIO(
                    tracemoe_sauce['preview']),
                                                        media_type='video/mp4')
                comment = api.update_status(
                    reply,
                    in_reply_to_status_id=tweet.id,
                    auto_populate_reply_metadata=True,
                    media_ids=[tw_response['media_id']],
                    possibly_sensitive=tracemoe_sauce['is_adult'])
            else:
                comment = api.update_status(reply,
                                            in_reply_to_status_id=tweet.id,
                                            auto_populate_reply_metadata=True)
        except tweepy.TweepError as error:
            if error.api_code == 186 and not requested:
                self.log.info(
                    "Post is too long; scrubbing bot instructions from message"
                )
                # noinspection PyUnboundLocalVariable
                comment = api.update_status(_reply,
                                            in_reply_to_status_id=tweet.id,
                                            auto_populate_reply_metadata=True)
            else:
                raise error

        # If we've been blocked by this user and have the artists Twitter handle, send the artist a DMCA guide
        if blocked:
            if twitter_sauce:
                self.log.warning(
                    f"Sending {twitter_sauce} DMCA takedown advice")
                api.update_status(
                    f"""{twitter_sauce} This account has stolen your artwork and blocked me for crediting you. このアカウントはあなたの絵を盗んで、私があなたを明記したらブロックされちゃいました
    https://github.com/FujiMakoto/twitter-saucenao/blob/master/DMCA.md
    https://help.twitter.com/forms/dmca""",
                    in_reply_to_status_id=comment.id,
                    auto_populate_reply_metadata=True)
            else:
                api.update_status(
                    f"This account has blocked {self.my.name}. For more information, please refer to:\n"
                    "https://github.com/FujiMakoto/twitter-saucenao#art-thieves-saucebot-has-been-blocked-by",
                    in_reply_to_status_id=comment.id,
                    auto_populate_reply_metadata=True)