예제 #1
0
    def _forceanalysis_callback(self, update: Update, context: CallbackContext, image_hash: str or None) -> None:
        """
        /forceanalysis command handler (with an argument)
        :param update: the chat update object
        :param context: telegram context
        """
        bot = context.bot
        message = update.effective_message
        chat_id = update.effective_chat.id

        with _session_scope() as session:
            if image_hash is not None:
                entity = self._persistence.find_by_image_hash(session, image_hash)
            elif message.reply_to_message is not None:
                reply_to_message = message.reply_to_message

                entity = self._find_entity_for_message(session, bot.id, reply_to_message)
            else:
                send_message(bot, chat_id,
                             ":exclamation: Missing image reply or image hash argument".format(image_hash),
                             reply_to=message.message_id)
                return

            if entity is None:
                send_message(bot, chat_id,
                             ":exclamation: Image entity not found".format(image_hash),
                             reply_to=message.message_id)
                return

            entity.analyser = None
            entity.analyser_quality = None
            self._persistence.update(session, entity)
            send_message(bot, chat_id,
                         ":wrench: Reset analyser data for image with hash: {})".format(entity.image_hash),
                         reply_to=message.message_id)
예제 #2
0
    def _send_random_quote(self, update: Update, context: CallbackContext) -> None:
        """
        Sends a quote from the pool to the requesting chat
        :param update: the chat update object
        :param context: telegram context
        """
        bot = context.bot
        chat_id = update.effective_chat.id
        bot.send_chat_action(chat_id=chat_id, action=ChatAction.TYPING)

        with _session_scope() as session:

            entity = self._persistence.get_random(session)
            LOGGER.debug("Sending random quote '{}' to chat id: {}".format(entity.image_hash, chat_id))

            caption = None
            if self._config.TELEGRAM_CAPTION_IMAGES_WITH_TEXT.value:
                caption = entity.text

            telegram_file_ids_for_current_bot = self.find_telegram_file_ids_for_current_bot(bot.token, entity)
            if len(telegram_file_ids_for_current_bot) > 0:
                file_ids = send_photo(bot=bot, chat_id=chat_id, file_id=telegram_file_ids_for_current_bot[0].id,
                                      caption=caption)
                bot_token = self._persistence.get_bot_token(session, bot.token)
                for file_id in file_ids:
                    entity.add_file_id(bot_token, file_id)
                self._persistence.update(session, entity)
                return

            image_bytes = self._persistence.get_image_data(entity)
            file_ids = send_photo(bot=bot, chat_id=chat_id, image_data=image_bytes, caption=caption)
            bot_token = self._persistence.get_bot_token(session, bot.token)
            for file_id in file_ids:
                entity.add_file_id(bot_token, file_id)
            self._persistence.update(session, entity, image_bytes)
예제 #3
0
    def _inline_query_callback(self, update: Update, context: CallbackContext) -> None:
        """
        Responds to an inline client request with a list of 16 randomly chosen images
        :param update: the chat update object
        :param context: telegram context
        """
        LOGGER.debug('Inline query')

        query = update.inline_query.query
        offset = update.inline_query.offset
        if not offset:
            offset = 0
        else:
            offset = int(offset)
        badge_size = self._config.TELEGRAM_INLINE_BADGE_SIZE.value

        with _session_scope() as session:
            if len(query) > 0:
                entities = self._persistence.find_by_text(session, query, badge_size, offset)
            else:
                entities = self._persistence.get_random(session, page_size=badge_size)

            results = list(map(lambda x: self._entity_to_inline_query_result(x), entities))
        
        LOGGER.debug('Inline query "{}": {}+{} results'.format(query, len(results), offset))
        if len(results) > 0:
            new_offset = offset + badge_size
        else:
            new_offset = ''

        update.inline_query.answer(
            results,
            next_offset=new_offset
        )
예제 #4
0
    def __init__(self, config: AppConfig, persistence: ImageDataPersistence,
                 image_analysers: [ImageAnalyser]):
        """
        Creates an instance
        :param config: the configuration
        :param persistence: the persistence
        :param image_analysers: available image analysers
        """
        super().__init__(config.IMAGE_ANALYSIS_INTERVAL.value)
        self._config = config
        self._persistence = persistence
        self._image_analysers = image_analysers

        if len(self._image_analysers) <= 0:
            LOGGER.warning("No image analyser provided")
            self._target_quality = 0
        else:
            self._target_quality = sorted(self._image_analysers,
                                          key=lambda x: x.get_quality(),
                                          reverse=True)[0].get_quality()

        with _session_scope() as session:
            self._not_optimal_ids = set(
                self._persistence.find_non_optimal(session,
                                                   self._target_quality))
예제 #5
0
    def start(self):
        if len(self._image_analysers) <= 0:
            LOGGER.warning("No image analyser provided, not starting.")
            return

        with _session_scope(False) as session:
            self._update_stats(session)

        super().start()
예제 #6
0
    def __init__(self, config: AppConfig, persistence: ImageDataPersistence,
                 bot: Bot):
        super().__init__(config.UPLOADER_INTERVAL.value)
        self._persistence = persistence
        self._bot = bot
        self._chat_id = config.UPLOADER_CHAT_ID.value

        with _session_scope() as session:
            self._not_uploaded_ids = set(
                self._persistence.get_not_uploaded_image_ids(
                    session, self._bot.token))
예제 #7
0
    def wrapper(self, update: Update, context: CallbackContext, *args, **kwargs):
        bot = context.bot
        message = update.effective_message
        chat_id = message.chat_id
        reply_to_message = message.reply_to_message

        with _session_scope(False) as session:
            entity = self._find_entity_for_message(session, bot.id, reply_to_message)
        if entity is None:
            send_message(bot, chat_id,
                         ":exclamation: You must directly reply to an image send by this bot to use reply commands.",
                         reply_to=message.message_id)

        # otherwise call wrapped function as normal
        return func(self, update, context, entity, *args, **kwargs)
예제 #8
0
    def _reply_text_command_callback(self, update: Update, context: CallbackContext,
                                     entity_of_reply: Image or None, text: str) -> None:
        """
        /text reply command handler
        :param update: the chat update object
        :param context: telegram context
        """
        bot = context.bot
        message = update.effective_message
        chat_id = update.effective_chat.id

        entity_of_reply.analyser = IMAGE_ANALYSIS_TYPE_HUMAN
        entity_of_reply.analyser_quality = 1.0
        entity_of_reply.text = text
        with _session_scope() as session:
            self._persistence.update(session, entity_of_reply)
        send_message(bot, chat_id,
                     ":wrench: Updated text for referenced image to '{}' (Hash: {})".format(entity_of_reply.text,
                                                                                            entity_of_reply.image_hash),
                     reply_to=message.message_id)
예제 #9
0
    def _reply_delete_command_callback(self, update: Update, context: CallbackContext,
                                       entity_of_reply: Image or None) -> None:
        """
        /text reply command handler
        :param update: the chat update object
        :param context: telegram context
        """
        bot = context.bot
        message = update.effective_message
        chat_id = update.effective_chat.id
        is_edit = hasattr(message, 'edited_message') and message.edited_message is not None

        if is_edit:
            LOGGER.debug("Ignoring edited delete command")
            return

        with _session_scope() as session:
            self._persistence.delete(session, entity_of_reply)
            send_message(bot, chat_id,
                         "Deleted referenced image from persistence (Hash: {})".format(entity_of_reply.image_hash),
                         reply_to=message.message_id)
예제 #10
0
    def _run(self):
        with _session_scope() as session:
            queue_length = len(self._not_uploaded_ids)
            UPLOADER_QUEUE_LENGTH.set(queue_length)
            if queue_length <= 0:
                # sleep for a longer time period to reduce load
                time.sleep(60)
                return

            image_id = self._not_uploaded_ids.pop()

            entity = self._persistence.get_image(session, image_id)
            image_data = self._persistence.get_image_data(entity)
            if image_data is None:
                LOGGER.warning(
                    "Missing image data for entity, trying to download: {}".
                    format(entity))
                try:
                    image_data = download_image_bytes(entity.url)
                    self._persistence.update(session, entity, image_data)
                    entity = self._persistence.get_image(session, image_id)
                except Exception as e:
                    LOGGER.error(
                        "Error trying to download missing image data for url '{}', deleting entity."
                        .format(entity.url), e)
                    self._persistence.delete(session, entity)
                    return

            file_ids = send_photo(bot=self._bot,
                                  chat_id=self._chat_id,
                                  image_data=image_data)
            bot_token = self._persistence.get_bot_token(
                session, self._bot.token)
            for file_id in file_ids:
                entity.add_file_id(bot_token, file_id)
            self._persistence.update(session, entity, image_data)
            LOGGER.debug(
                "Send image '{}' to chat '{}' and updated entity with file_id {}."
                .format(entity.url, self._chat_id, file_ids))
예제 #11
0
    def _run(self):
        """
        The job that is executed regularly by this crawler
        """
        with _session_scope() as session:
            queue_length = len(self._not_optimal_ids)
            IMAGE_ANALYSIS_QUEUE_LENGTH.set(queue_length)
            if queue_length <= 0:
                # sleep for a longer time period to reduce load
                time.sleep(60)
                self._not_optimal_ids = set(
                    self._persistence.find_non_optimal(session,
                                                       self._target_quality))
                return

            entity = None
            while entity is None:
                if queue_length <= 0:
                    return
                image_id = self._not_optimal_ids.pop()
                queue_length -= 1
                IMAGE_ANALYSIS_QUEUE_LENGTH.set(queue_length)
                entity = self._persistence.get_image(session, image_id)
                if entity is None:
                    LOGGER.warning(
                        f"Image id scheduled for analysis not found: {image_id}"
                    )
                    # the entity has probably been removed in the meantime
                    continue

            analyser = select_best_available_analyser(session,
                                                      self._image_analysers,
                                                      self._persistence)
            if analyser is None:
                # No analyser available, skipping
                # sleep for a longer time period to reduce db load
                time.sleep(60)
                return

            if entity.analyser_quality is not None and entity.analyser_quality >= analyser.get_quality(
            ):
                LOGGER.debug(
                    "Not analysing '{}' with '{}' because it wouldn't improve analysis quality ({} vs {})"
                    .format(entity.url, analyser.get_identifier(),
                            entity.analyser_quality, analyser.get_quality()))
                # sleep for a longer time period to reduce db load
                time.sleep(60)
                return

            image_data = self._persistence.get_image_data(entity)
            if image_data is None:
                LOGGER.warning(
                    "No image data found for entity with image_hash {}, it will not be analysed."
                    .format(entity.image_hash))
                try:
                    image_data = download_image_bytes(entity.url)
                    self._persistence.update(session, entity, image_data)
                except Exception as e:
                    # if len(entity.telegram_ids) > 0:
                    #     LOGGER.warning(
                    #         "Error downloading image data from original source, using telegram upload instead. {}".format(
                    #             entity))
                    #     # TODO:
                    # else:
                    LOGGER.error(
                        "Error trying to download missing image data for url '{}', deleting entity."
                        .format(entity.url), e)
                    self._persistence.delete(session, entity)
                return

            old_analyser = entity.analyser
            old_quality = entity.analyser_quality
            if old_quality is None:
                old_quality = 0

            entity.analyser = analyser.get_identifier()
            entity.analyser_quality = analyser.get_quality()
            new_text = analyser.find_text(image_data)

            if (new_text is None or len(new_text) <= 0
                ) and entity.text is not None and len(entity.text) > 0:
                LOGGER.debug(
                    "Ignoring new analysis text because it would delete it")
            else:
                entity.text = new_text

            self._persistence.update(session, entity, image_data)
            LOGGER.debug(
                "Updated analysis of '{}' with '{}' (was '{}') with a quality improvement of {} ({} -> {}): {}"
                .format(entity.url, analyser.get_identifier(), old_analyser,
                        entity.analyser_quality - old_quality, old_quality,
                        entity.analyser_quality,
                        format_for_single_line_log(entity.text)))

            self._update_stats(session)