Search code examples
pythontelegrampython-telegram-bot

Send a local photo from inline mode in a telegram bot


I use Python telegram bot API for my bot.

I want to generate photos locally and send them as inline results but InlineQueryResultPhoto accepts only photo URLs.

Suppose my project structure looks like this:

main.py
photo.jpg

How do I send photo.jpg as an inline result?

Here is the code of main.py:

from uuid import uuid4
from telegram.ext import InlineQueryHandler, Updater
from telegram import InlineQueryResultPhoto


def handle_inline_request(update, context):
    update.inline_query.answer([
        InlineQueryResultPhoto(
            id=uuid4(),
            photo_url='',  # WHAT DO I PUT HERE?
            thumb_url='',  # AND HERE?
        )
    ])


updater = Updater('TELEGRAM_TOKEN', use_context=True)
updater.dispatcher.add_handler(InlineQueryHandler(handle_inline_request))

updater.start_polling()
updater.idle()

Solution

  • There is no direct answer because Telegram Bot API doesn't provide it. But there are two workaounds: you can use upload a photo to telegram servers and then use InlineQueryResultCachedPhoto or you can upload to any image server and then use InlineQueryResultPhoto.

    InlineQueryResultCachedPhoto

    This first option requires you to previously upload the photo to telegram servers before creating the result list. Which options do you have? The bot can message you the photo, get that information and use what you need. Another option is creating a private channel where your bot can post the photos it will reuse. The only detail of this method is getting to know the channel_id (How to obtain the chat_id of a private Telegram channel?).

    Now lets see some code:

    from config import tgtoken, privchannID
    from uuid import uuid4
    from telegram import Bot, InlineQueryResultCachedPhoto
    
    bot = Bot(tgtoken)
    def inlinecachedphoto(update, context):
        query = update.inline_query.query
        if query == "/CachedPhoto":
            infophoto = bot.sendPhoto(chat_id=privchannID,photo=open('logo.png','rb'),caption="some caption")
            thumbphoto = infophoto["photo"][0]["file_id"]
            originalphoto = infophoto["photo"][-1]["file_id"]
            results = [
                InlineQueryResultCachedPhoto(
                    id=uuid4(),
                    title="CachedPhoto",
                    photo_file_id=originalphoto)
                ]
            update.inline_query.answer(results)
    

    when you send a photo to a chat/group/channel, you can obtain the file_id, the file_id of the thumbnail, the caption and other details I'm going to skip. What the problem? If you don't filter the right query, you may end up sending the photo multiple times to your private channel. It also means the autocomplete won't work.

    InlineQueryResultPhoto

    The other alternative is upload the photo to internet and then use the url. Excluding options like your own hosting, you can use some free image hostings that provides APIs (for example: imgur, imgbb). For this code, generating your own key in imgbb is simpler than imgur. Once generated:

    import requests
    import json
    import base64
    from uuid import uuid4
    from config import tgtoken, key_imgbb
    from telegram import InlineQueryResultPhoto
    
    def uploadphoto():
        with open("figure.jpg", "rb") as file:
            url = "https://api.imgbb.com/1/upload"
            payload = {
                "key": key_imgbb,
                "image": base64.b64encode(file.read()),
            }
            response = requests.post(url, payload)
            if response.status_code == 200:
                return {"photo_url":response.json()["data"]["url"], "thumb_url":response.json()["data"]["thumb"]["url"]}
        return None
    
    def inlinephoto(update, context):
        query = update.inline_query.query
        if query == "/URLPhoto":
            upphoto = uploadphoto()
            if upphoto:
                results = [
                    InlineQueryResultPhoto(
                        id=uuid4(),
                        title="URLPhoto",
                        photo_url=upphoto["photo_url"],
                        thumb_url=upphoto["thumb_url"])
                    ]
                update.inline_query.answer(results)
    

    This code is similar to the previous method (and that includes the same problems): uploading multiple times if you don't filter the query and you won't have the autocomplete when writing the inline.

    Disclaimer

    Both code were written thinking the images you want to upload are generated at the moment you receive the query, otherwise you can do the work previous to receiving the query, saving that info in a database.

    Bonus

    You can run your own bot to get the channel_id of your private channel with pyTelegramBotAPI

    import telebot
    
    bot = telebot.TeleBot(bottoken)
    
    @bot.channel_post_handler(commands=["getchannelid"])
    def chatid(message):
        bot.reply_to(message,'channel_id = {!s}'.format(message.chat.id))
    
    bot.polling()
    

    To get the id you need to write in the channel /getchannelid@botname