Esempio n. 1
0
def send_emails_telegram(bot: TeleBot, chat_id: str):
    os.chdir("INBOX")  # Переход в папку с сохраненными письмами
    for mail in os.listdir("."):  # Получаем список папок, в которых лежит само письмо и его влложения
        os.chdir(mail)  # Переходим в папку с письмом

        if os.path.exists("text_plain.txt"):  # Если существует текстовая версия письма
            with open("text_plain.txt") as f:
                message = split(f.read())
            bot.send_message(chat_id, message[0])  # Отправляем ее первые 4092 символа
            if len(message) >= 2:  # И высылаем файл письма целиком (если письмо содержит более 4092 символов)
                bot.send_document(chat_id, open("text_plain.txt", "rb"), caption="Продолжение письма")
            os.remove("text_plain.txt")  # Удаляем отправленный файл

        if os.path.exists("text_html.html"):  # Если существует веб версия версия письма, то отправляем ее файлом
            bot.send_document(chat_id, open("text_html.html", "rb"), caption="{} (веб версия письма)".format(mail))
            os.remove("text_html.html")  # Удаляем отправленный файл

        if os.path.exists("attachments"):  # Если есть вложения в письме, то отправляем и их.
            for file in os.listdir("attachments"):
                bot.send_document(chat_id, open(file, "rb"))
                os.remove(file)  # Удаляем отправленный файл
            os.rmdir("attachments")  # Удаляем пустую папку

        os.chdir("../")  # Выходим из папки с письмом
        os.rmdir(mail)  # И удаляем ее, так как она пуста
    os.chdir("../")  # Выходим из папки с сохраннеными письмами
Esempio n. 2
0
    def send(self, bot: TeleBot, group):
        msg: Message
        if self.type == 'text':
            msg = bot.send_message(group, self.text)
        elif self.type == 'photo':
            msg = bot.send_photo(group, self.photo_id, self.caption)
        elif self.type == 'video':
            msg = bot.send_video(group, self.video_id, caption=self.caption)
        elif self.type == 'document':
            msg = bot.send_document(group, self.doc_id, caption=self.caption)
        else:
            print('А где содержимое письма?!')

        return msg
Esempio n. 3
0
def form_and_send_new_cv_archive(bot: TeleBot, user: User):
    UPDATE_INTERVAL = 1  # minutes
    ejf = JobFair.objects.first()
    chat_id = user.chat_id

    # update archive only once in 10 min
    archive_last_update = ejf.cv_archive_last_update
    if archive_last_update:
        date_diff = (user.last_interaction_date -
                     archive_last_update).total_seconds() / 60.0
        if date_diff < UPDATE_INTERVAL:
            bot.send_message(
                chat_id,
                text=f"Почекай ще {UPDATE_INTERVAL-date_diff:.2f} хвилин...",
            )
            return

    # clear archives list
    ejf.cv_archive_file_id_list = list()

    for temp_archive_path in _form_max_size_archive(bot):
        # show sending document
        bot.send_chat_action(chat_id, action="upload_document")

        # send archive
        with open(temp_archive_path, "rb") as archive:
            message = bot.send_document(chat_id, archive)

        # update db info
        ejf.cv_archive_file_id_list += [message.document.file_id]

        # delete archive
        os.remove(temp_archive_path)

    # update db info
    ejf.cv_archive_size = User.objects.filter(cv_file_id__ne=None).count()
    ejf.cv_archive_last_update = user.last_interaction_date
    ejf.save()
Esempio n. 4
0
def send_email_telegram(bot: TeleBot, chat_id: str,
                        mail: mailparser.MailParser):
    subject = mail.subject
    from_ = " ".join(mail.from_[0])
    mail_name = "{} от {}".format(subject, from_)

    if mail.text_plain:  # Если существует текстовая версия письма
        message = "\n".join(mail.text_plain)
        bot.send_message(
            chat_id,
            split(message)[0])  # Отправляем ее первые 4092 символа
        # И высылаем файл письма целиком (если письмо содержит более 4092 символов)
        if len(message) >= 2:
            f = io.BytesIO("{}\n\n{}".format(mail_name, message).encode())
            f.name = mail_name + ".txt"
            bot.send_document(chat_id, f, caption="Полный текст письма")

    if mail.text_html:  # Если существует веб версия письма, то отправляем ее файлом
        text_html = "\n".join(mail.text_html)
        f = io.BytesIO(text_html.encode())
        f.name = mail_name + ".html"
        bot.send_document(
            chat_id,
            f,
            caption="{} (веб версия письма)".format("{} от {}".format(
                subject, from_)),
        )

    if mail.attachments:  # Если есть вложения в письме, то отправляем и их.
        with tempfile.TemporaryDirectory(
        ) as tmp_dir:  # Создание временной директории для хранения вложений
            mail.write_attachments(tmp_dir)  # Сохраняем вложения
            for file in os.listdir(tmp_dir):
                bot.send_document(chat_id,
                                  open(os.path.join(tmp_dir, file),
                                       "rb"))  # Отправляем вложения
Esempio n. 5
0
class TgBot(threading.Thread):
    def loadCommandConfig(self):
        pass

    def loadPersonConfig(self):
        persons = {}
        return persons

    def __init__(self, config, admins, tunnels, loopDelay=0.1, debug=False):
        super(TgBot, self).__init__()
        self.debug = debug
        self.stopped = Event()
        self.loopDelay = loopDelay

        self.admins = admins
        self.tunnels = tunnels
        self.config = config
        self.botUserName = config['botUserName']
        self.bot = TeleBot(config['token'])

        self.commands = [
            # AddAdmin(bot=self, cmd=['add-admin']),
            # Alarm('alarm', time.time())
            Bind(bot=self, cmd=['b', 'bind']),
            ListBind(bot=self, cmd=['lb', 'listbind', 'list-bind']),
            Toggle(bot=self, cmd=['t', 'toggle']),
            ListToggle(bot=self, cmd=['lt', 'listtoggle', 'list-toggle']),
            UnBind(bot=self, cmd=['ub', 'unbind', 'un-bind'])
        ]
        self.persons = self.loadPersonConfig()

    def sendMessage(self, _id, _msg, parse_mode=None):
        self.bot.send_message(chat_id=_id, text=_msg, parse_mode=parse_mode)

    def replyTo(self, msg, _msg):
        self.bot.reply_to(msg, _msg)

    def listbind_handler(self, message):
        print(message)

    def handler(self, msg):
        for each in msg:
            try:
                _from = each.from_user
                _chat = each.chat
                if each.text and each.text.startswith('#'):
                    _text = each.text
                    for tunnel in getMatchTunnels(self.tunnels, tgId=_chat.id):
                        if tunnel.tg['toggle']:
                            name = _from.username or _from.first_name or _from.last_name
                            message = '[Anonymous]: {0}'.format(
                                _text[2:]) if _text.startswith(
                                    '##') else '[{0}]: {1}'.format(
                                        name, _text[1:])
                            tunnel.tk['queue'].put(message)
                if self.debug:
                    print(each)
            except Exception as e:
                if self.debug:
                    traceback.print_exc()

    def queueHandler(self):
        while not self.stopped.wait(self.loopDelay):
            for each in self.tunnels:
                tg = each.tg
                while not tg['queue'].empty() and tg['toggle']:
                    chatId = tg['id']
                    msg = tg['queue'].get()
                    try:
                        if '<img' in msg:
                            link = re.search('src=\".*?\"', msg).group(0)[5:-1]
                            if '.gif' in msg:
                                self.bot.send_document(chatId, link)
                            else:
                                self.bot.send_photo(chatId, link)
                        elif '</' in msg:
                            self.bot.send_message(chatId,
                                                  msg,
                                                  parse_mode='HTML')
                        else:
                            self.bot.send_message(chatId,
                                                  msg,
                                                  parse_mode='Markdown')
                    except Exception as e:
                        self.bot.send_message(chatId, msg)
                        if self.debug:
                            traceback.print_exc()

    def start(self):
        super(TgBot, self).start()
        for cmd in self.commands:
            cmd.register()
        self.bot.set_update_listener(self.handler)
        thread = threading.Thread(target=self.queueHandler)
        thread.start()

    def run(self):
        while not self.stopped.wait(self.loopDelay):
            try:
                self.bot.polling(none_stop=False)
            except:
                if self.debug:
                    traceback.print_exc()

    def stop(self):
        self.bot.stop_bot()
        self.stopped.set()
Esempio n. 6
0
def copy_message(bot: telebot.TeleBot, msg: telebot.types.Message, chat_ids: list, disable_notification=False,
                 keyboard=None):
    last_id = chat_ids[-1]
    if len(chat_ids) > 1:
        chat_ids = chat_ids[:-1]
    else:
        chat_ids = []
    if msg.content_type == 'text':
        text = check_msg_entities(msg.entities, msg.html_text)
        for chat_id in chat_ids:
            try:
                bot.send_message(chat_id,
                                 text=text,
                                 parse_mode='html',
                                 disable_notification=disable_notification,
                                 reply_markup=keyboard
                                 )
            except telebot.apihelper.ApiException as e:
                send_error(bot, e)
                continue
            sleep(0.5)
        return bot.send_message(last_id,
                                text=text,
                                parse_mode='html',
                                disable_notification=disable_notification,
                                reply_markup=keyboard
                                )
    else:
        caption = check_msg_entities(msg.entities, msg.html_caption)
        if msg.content_type == 'photo':
            size = msg.photo[-1]
            for chat_id in chat_ids:
                try:
                    bot.send_photo(chat_id,
                                   photo=size.file_id,
                                   caption=caption,
                                   parse_mode='html',
                                   disable_notification=disable_notification,
                                   reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.send_photo(last_id,
                                  photo=size.file_id,
                                  caption=caption,
                                  parse_mode='html',
                                  disable_notification=disable_notification,
                                  reply_markup=keyboard)
        elif msg.content_type == 'audio':
            for chat_id in chat_ids:
                try:
                    bot.send_audio(chat_id,
                                   audio=msg.audio.file_id,
                                   caption=caption,
                                   parse_mode='html',
                                   disable_notification=disable_notification,
                                   reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.send_audio(last_id,
                                  audio=msg.audio.file_id,
                                  caption=caption,
                                  parse_mode='html',
                                  disable_notification=disable_notification,
                                  reply_markup=keyboard)
        elif msg.content_type == 'document':
            for chat_id in chat_ids:
                try:
                    bot.send_document(chat_id,
                                      data=msg.document.file_id,
                                      caption=caption,
                                      parse_mode='html',
                                      disable_notification=disable_notification,
                                      reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
                return bot.send_document(last_id,
                                         data=msg.document.file_id,
                                         caption=caption,
                                         parse_mode='html',
                                         disable_notification=disable_notification,
                                         reply_markup=keyboard)
        elif msg.content_type == 'sticker':
            for chat_id in chat_ids:
                try:
                    bot.send_sticker(chat_id,
                                     data=msg.sticker.file_id,
                                     disable_notification=disable_notification,
                                     reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
            sleep(0.5)
            return bot.send_sticker(last_id,
                                    data=msg.sticker.file_id,
                                    disable_notification=disable_notification,
                                    reply_markup=keyboard)
        elif msg.content_type == 'video':
            for chat_id in chat_ids:
                try:
                    bot.send_video(chat_id,
                                   data=msg.video.file_id,
                                   caption=caption,
                                   parse_mode='html',
                                   disable_notification=disable_notification,
                                   reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.send_video(last_id,
                                  data=msg.video.file_id,
                                  caption=caption,
                                  parse_mode='html',
                                  disable_notification=disable_notification,
                                  reply_markup=keyboard)
        elif msg.content_type == 'animation':
            for chat_id in chat_ids:
                try:
                    bot.send_animation(chat_id,
                                       animation=msg.animation.file_id,
                                       caption=caption,
                                       parse_mode='html',
                                       disable_notification=disable_notification,
                                       reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.send_animation(last_id,
                                      animation=msg.animation.file_id,
                                      caption=caption,
                                      parse_mode='html',
                                      disable_notification=disable_notification,
                                      reply_markup=keyboard)
        elif msg.content_type == 'voice':
            for chat_id in chat_ids:
                try:
                    bot.send_voice(chat_id,
                                   voice=msg.voice.file_id,
                                   caption=caption,
                                   parse_mode='html',
                                   disable_notification=disable_notification,
                                   reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.send_voice(last_id,
                                  voice=msg.voice.file_id,
                                  caption=caption,
                                  parse_mode='html',
                                  disable_notification=disable_notification,
                                  reply_markup=keyboard)
        elif msg.content_type == 'video_note':
            for chat_id in chat_ids:
                try:
                    bot.send_video_note(chat_id,
                                        data=msg.video_note.file_id,
                                        disable_notification=disable_notification,
                                        reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.send_video_note(last_id,
                                       data=msg.video_note.file_id,
                                       disable_notification=disable_notification,
                                       reply_markup=keyboard)
        elif msg.content_type == 'contact':
            for chat_id in chat_ids:
                try:
                    bot.send_contact(chat_id,
                                     phone_number=msg.contact.phone_number,
                                     first_name=msg.contact.first_name,
                                     last_name=msg.contact.last_name or '',
                                     disable_notification=disable_notification,
                                     reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.send_contact(last_id,
                                    phone_number=msg.contact.phone_number,
                                    first_name=msg.contact.first_name,
                                    last_name=msg.contact.last_name or '',
                                    disable_notification=disable_notification,
                                    reply_markup=keyboard)
        elif msg.content_type == 'location':
            for chat_id in chat_ids:
                try:
                    bot.send_location(chat_id,
                                      latitude=msg.location.latitude,
                                      longitude=msg.location.longitude,
                                      disable_notification=disable_notification,
                                      reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.send_location(last_id,
                                     latitude=msg.location.latitude,
                                     longitude=msg.location.longitude,
                                     disable_notification=disable_notification,
                                     reply_markup=keyboard)
        elif msg.content_type == 'venue':
            for chat_id in chat_ids:
                try:
                    bot.send_venue(chat_id,
                                   latitude=msg.venue.location.latitude,
                                   longitude=msg.venue.location.longitude,
                                   title=msg.venue.title,
                                   address=msg.venue.address,
                                   foursquare_id=msg.venue.foursquare_id,
                                   disable_notification=disable_notification,
                                   reply_markup=keyboard)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.send_venue(last_id,
                                  latitude=msg.venue.location.latitude,
                                  longitude=msg.venue.location.longitude,
                                  title=msg.venue.title,
                                  address=msg.venue.address,
                                  foursquare_id=msg.venue.foursquare_id,
                                  disable_notification=disable_notification,
                                  reply_markup=keyboard)
        elif msg.content_type == 'poll':
            for chat_id in chat_ids:
                try:
                    bot.forward_message(chat_id, msg.chat.id, msg.message_id)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.forward_message(last_id, msg.chat.id, msg.message_id)
        elif msg.content_type == 'game':
            for chat_id in chat_ids:
                try:
                    bot.forward_message(chat_id, msg.chat.id, msg.message_id)
                except telebot.apihelper.ApiException as e:
                    send_error(bot, e)
                    continue
                sleep(0.5)
            return bot.forward_message(last_id, msg.chat.id, msg.message_id)
    raise ValueError('Can\'t copy this message')
Esempio n. 7
0
class ColorCodeBot:
    def __init__(self,
                 api_key: str,
                 lang: Mapping[str, str],
                 theme_image_ids: tuple[str],
                 keyboards: Mapping[str, InlineKeyboardMarkup],
                 guesslang_syntaxes: Mapping[str, str],
                 *args: Any,
                 admin_chat_id: Optional[str] = None,
                 db_path: str = str(
                     local.path(__file__).up() / 'user_themes.sqlite'),
                 **kwargs: Any):
        self.lang = lang
        self.theme_image_ids = theme_image_ids
        self.kb = keyboards
        self.guesslang_syntaxes = guesslang_syntaxes
        self.admin_chat_id = admin_chat_id
        self.db_path = db_path
        self.user_themes = KeyValue(key_field=IntegerField(primary_key=True),
                                    value_field=CharField(),
                                    database=APSWDatabase(db_path))
        self.log = mk_logger()
        self.bot = TeleBot(api_key, *args, **kwargs)
        self.register_handlers()
        self.guesser = Guess()

    def register_handlers(self):
        self.welcome = self.bot.message_handler(commands=['start', 'help'])(
            self.welcome)
        self.browse_themes = self.bot.message_handler(
            commands=['theme', 'themes'])(self.browse_themes)
        self.mk_theme_previews = self.bot.message_handler(
            commands=['previews'])(self.mk_theme_previews)
        self.intake_snippet = self.bot.message_handler(
            func=lambda m: m.content_type == 'text')(self.intake_snippet)
        self.recv_photo = self.bot.message_handler(content_types=['photo'])(
            self.recv_photo)
        self.restore_kb = self.bot.callback_query_handler(
            lambda q: yload(q.data)['action'] == 'restore')(self.restore_kb)
        self.set_snippet_filetype = self.bot.callback_query_handler(
            lambda q: yload(q.data)['action'] == 'set ext')(
                self.set_snippet_filetype)
        self.set_theme = self.bot.callback_query_handler(
            lambda q: yload(q.data)['action'] == 'set theme')(self.set_theme)
        self.send_photo_elsewhere = self.bot.inline_handler(
            lambda q: q.query.startswith("img "))(self.send_photo_elsewhere)
        self.switch_from_inline = self.bot.inline_handler(lambda q: True)(
            self.switch_from_inline)

    @retry
    def switch_from_inline(self, inline_query: InlineQuery):
        self.log.msg("receiving inline query",
                     user_id=inline_query.from_user.id,
                     user_first_name=inline_query.from_user.first_name,
                     query=inline_query.query)
        self.bot.answer_inline_query(
            inline_query.id, [],
            switch_pm_text=self.lang['switch to direct'],
            switch_pm_parameter='x')

    @retry
    def welcome(self, message: Message):
        self.log.msg("introducing myself",
                     user_id=message.from_user.id,
                     user_first_name=message.from_user.first_name,
                     chat_id=message.chat.id)
        self.bot.reply_to(
            message,
            self.lang['welcome'],
            reply_markup=ForceReply(
                input_field_placeholder=self.lang['input field placeholder']))

    @retry
    def mk_theme_previews(self, message: Message):
        if not self.admin_chat_id or str(
                message.chat.id) != self.admin_chat_id:
            self.log.msg("naughty preview attempt",
                         user_id=message.from_user.id,
                         user_first_name=message.from_user.first_name,
                         chat_id=message.chat.id,
                         admin_chat_id=self.admin_chat_id)
            return
        sample_code = dedent("""
            # palinDay :: Int -> [ISO Date]
            def palinDay(y):
                '''A possibly empty list containing the palindromic
                   date for the given year, if such a date exists.
                '''
                s = str(y)
                r = s[::-1]
                iso = '-'.join([s, r[0:2], r[2:]])
                try:
                    datetime.strptime(iso, '%Y-%m-%d')
                    return [iso]
                except ValueError:
                    return []
        """)
        for button in chain.from_iterable(self.kb['theme'].keyboard):
            theme = button.text
            html = mk_html(f"# {theme}{sample_code}", 'py', theme)
            with local.tempdir() as folder:
                png_path = mk_png(html, folder=folder)
                send_image(bot=self.bot,
                           chat_id=message.chat.id,
                           png_path=png_path,
                           reply_msg_id=message.message_id)

    @retry
    def browse_themes(self, message: Message):
        self.log.msg("browsing themes",
                     user_id=message.from_user.id,
                     user_first_name=message.from_user.first_name,
                     chat_id=message.chat.id)
        albums = [
            self.theme_image_ids[i:i + 10]
            for i in range(0, len(self.theme_image_ids), 10)
        ]
        for album in albums:
            self.bot.send_media_group(message.chat.id,
                                      map(InputMediaPhoto, album),
                                      reply_to_message_id=message.message_id)
        self.bot.reply_to(message,
                          self.lang['select theme'],
                          reply_markup=self.kb['theme'])

    @retry
    def set_theme(self, cb_query: CallbackQuery):
        data = yload(cb_query.data)
        user = cb_query.message.reply_to_message.from_user
        self.log.msg("setting theme",
                     user_id=user.id,
                     user_first_name=user.first_name,
                     theme=data['theme'],
                     chat_id=cb_query.message.chat.id)
        self.bot.edit_message_reply_markup(cb_query.message.chat.id,
                                           cb_query.message.message_id,
                                           reply_markup=minikb('theme'))
        self.user_themes[user.id] = data['theme']
        self.bot.answer_callback_query(
            cb_query.id,
            text=self.lang['acknowledge theme'].format(data['theme']))
        if self.admin_chat_id:
            with open(self.db_path, 'rb') as doc:
                self.bot.send_document(self.admin_chat_id, doc)

    def guess_ext(self,
                  code: str,
                  probability_min: float = .12) -> Optional[str]:
        syntax, probability = self.guesser.probabilities(code)[0]
        ext = self.guesslang_syntaxes.get(syntax)
        self.log.msg("guessed syntax",
                     probability_min=probability_min,
                     probability=probability,
                     syntax=syntax,
                     ext=ext)
        if probability >= probability_min:
            return ext
        for start, ext in {
                '{': 'json',
                '---\n': 'yaml',
                '[[': 'toml',
                '[': 'ini',
                '<?php': 'php',
                '<': 'xml',
                '-- ': 'lua'
        }.items():
            if code.startswith(start):
                return ext

    @retry
    def intake_snippet(self, message: Message):
        self.log.msg("receiving code",
                     user_id=message.from_user.id,
                     user_first_name=message.from_user.first_name,
                     chat_id=message.chat.id)
        ext = self.guess_ext(message.text)
        if ext:
            kb_msg = self.bot.reply_to(
                message,
                f"{self.lang['query ext']}\n\n{self.lang['guessed syntax'].format(ext)}",
                reply_markup=minikb('syntax', self.lang['syntax picker']),
                parse_mode='Markdown',
                disable_web_page_preview=True)
            self.set_snippet_filetype(cb_query=None,
                                      query_message=kb_msg,
                                      ext=ext)
        else:
            self.bot.reply_to(message,
                              self.lang['query ext'],
                              reply_markup=self.kb['syntax'],
                              parse_mode='Markdown',
                              disable_web_page_preview=True)

    @retry
    def send_photo_elsewhere(self, inline_query: InlineQuery):
        file_id = inline_query.query.split('img ', 1)[-1]
        self.log.msg("creating inline query result",
                     file_id=file_id,
                     file_info=self.bot.get_file(file_id))
        self.bot.answer_inline_query(inline_query.id, [
            InlineQueryResultCachedPhoto(
                id=str(uuid4()), photo_file_id=file_id, title="Send Image")
        ],
                                     is_personal=True)

    @retry
    def restore_kb(self, cb_query: CallbackQuery):
        data = yload(cb_query.data)
        self.bot.edit_message_reply_markup(
            cb_query.message.chat.id,
            cb_query.message.message_id,
            reply_markup=self.kb[data['kb_name']])
        self.bot.answer_callback_query(cb_query.id)

    @retry
    def set_snippet_filetype(self,
                             cb_query: Optional[CallbackQuery] = None,
                             query_message: Optional[Message] = None,
                             ext: Optional[str] = None):
        if cb_query:
            query_message = cb_query.message
            ext = yload(cb_query.data)['ext']
        elif not (query_message and ext):
            raise Exception(
                "Either cb_query or both query_message and ext are required")
        self.log.msg("colorizing code",
                     user_id=query_message.reply_to_message.from_user.id,
                     user_first_name=query_message.reply_to_message.from_user.
                     first_name,
                     syntax=ext,
                     chat_id=query_message.chat.id)
        if cb_query:
            self.bot.edit_message_reply_markup(query_message.chat.id,
                                               query_message.message_id,
                                               reply_markup=minikb(
                                                   'syntax',
                                                   self.lang['syntax picker']))
        snippet = query_message.reply_to_message
        theme = self.user_themes.get(snippet.from_user.id,
                                     'base16/gruvbox-dark-hard')

        html = mk_html(snippet.text, ext, theme)
        send_html(bot=self.bot,
                  chat_id=snippet.chat.id,
                  html=html,
                  reply_msg_id=snippet.message_id)

        with local.tempdir() as folder:
            png_path = mk_png(html, folder=folder)
            did_send = False
            if len(snippet.text.splitlines()) <= 30:
                try:
                    photo_msg = send_image(bot=self.bot,
                                           chat_id=snippet.chat.id,
                                           png_path=png_path,
                                           reply_msg_id=snippet.message_id)
                except ApiException as e:
                    self.log.error("failed to send compressed image",
                                   exc_info=e,
                                   chat_id=snippet.chat.id)
                else:
                    did_send = True
                    kb_to_chat = InlineKeyboardMarkup()
                    kb_to_chat.add(
                        InlineKeyboardButton(
                            self.lang['send to chat'],
                            switch_inline_query=
                            f"img {photo_msg.photo[-1].file_id}"))
                    self.bot.edit_message_reply_markup(photo_msg.chat.id,
                                                       photo_msg.message_id,
                                                       reply_markup=kb_to_chat)
            if not did_send:
                send_image(bot=self.bot,
                           chat_id=snippet.chat.id,
                           png_path=png_path,
                           reply_msg_id=snippet.message_id,
                           compress=False)

        if cb_query:
            self.bot.answer_callback_query(cb_query.id)

    def recv_photo(self, message: Message):
        self.log.msg('received photo',
                     file_id=message.photo[0].file_id,
                     user_id=message.from_user.id,
                     user_first_name=message.from_user.first_name,
                     chat_id=message.chat.id)