How to send photos to Telegram in Ruby
One of the nice things about APIs is that you can bolt disperate systems together to do quite complex things simply.
Well, that’s the theory. On a project I’m working on currently I want to be able to send photos to Telegram. The project uses Rails 6. Photos are JPEGs and are retrieved from an external server over a SOAP interface (not recommended) in base64 format and then need to be sent to Telegram.
Ruby has lots of plug-ins which extend functionality — ‘Gems’. For this project I’ve used telegram-bot-ruby. Other Gems are available, but this one had the clearest documentation.
So, let’s step through the process:
a) First, you’ll need an API key to access the system.
b) You will do this by setting up a Bot in Telegram — and using that key.
https://www.sitepoint.com/quickly-create-a-telegram-bot-in-ruby/ is an excellent article which steps through these points.
Now, we want to setup a class to control Telegram. This is simple. In the class we want:
a) A client which will set up a Telegram connection when called.
b) A method which will will receive a base64 string and a message string, process them and send them to Telegram.
For the client, it’s probably best to set it up when a new instance of our controller is called.
Also, for our bot we are going to need to specify where we are going to send the photo message to.
Specifying Our Destination
In my application, I set up a group for messages. In order to send messages to that group I need the group ID. This is a negative number.
Stack Overflow here: https://stackoverflow.com/questions/32423837/telegram-bot-how-to-get-a-group-chat-id explains how to find it.
To summarise:
a) Add the bot to the Group — by going to the group. Click ‘add members’ — in the dialogue box, type in the Bot’s name. Then click ‘add’.
b) Send a dummy message to the bot from your own account. You MUST do this or the next step won’t work. The example given is the message /my_id @my_bot
c) Go to the following URL; replace xxxx with the bot token you got when you created the bot: https://api.telegram.org/xxx/getUpdates
d) You’ll see a JSON object with “chat”:{“id”:-zzzzzz
The -zzzzz will be Group ID.
Building Telegram Controller Class
In our class we are going to:
require: ‘telegram/bot’
so that we can use Telegram
require: ‘base64’
so that we can convert our base64 text to binary.
For our constructor method we need to:
a) Check we have a API key present on our machine — and raise an error if it isn’t.
b) Set up a client with the key
In my example, I’ve set the API key as an environmental variable TELEGRAM_KEY :
def initialize(chat_id = xxxxx)
@chat_id = chat_id
bot = Telegram::Bot::Api
raise ‘API key for Telegram not set. export TELEGRAM_KEY=[key]’ if ENV[‘TELEGRAM_KEY’].nil? token = ENV[‘TELEGRAM_KEY’]
@client = bot.new(token)
end
In the code above:
a) We assume that the chat id for our group is the xxxxx above. The dependency injection in there allows this to be overriden if necessary.
b) We throw an error if an API hasn’t been set.
Receiving and processing text and base64 strings
I’ve assumed that any other class which calls TelegramController does so using a hash. The hash has two keys:
text — This contains the text of anything we want to send with the photo. It will appear as a caption to the photo and can be a maximum of 1024 characters.
photo — This is the base 64 encoded JPEG string we want to send.
Here’s the code for our method — or at least the top level method:
def send_photo_message(hash)
caption = hash[:text]
photo = hash[:photo]
photo_stream = create_image_stream(photo)
photo_message(caption, photo_stream)
end
We assign the text and photo strings to local variables. To process the photo string we call create_image_stream
def create_image_stream(photo)
binary = Base64.strict_decode64(photo)
StringIO.new(binary)
end
We do two things in this method:
i) Convert our base64 encoded stream to a binary.
ii) Convert our binary string to StringIO object. This is necessary as Faraday (see below) can’t process it otherwise.
Our method returns the binary StringIO object to the send_photo_message
method. We know need to send the binary and text to Telegram. We do this with the photo_message
method:
def photo_message(caption, photo_stream)
@client.send_photo(chat_id: @chat_id, caption: caption,
photo: Faraday::FilePart.new(photo_stream,'image/jpeg'))
end
We defined @client
when we called a new instance of TelegramController
— send_photo
is a method inside Telegram.
We specify the parameters for the api as symbols. These are passed as snake_case
.
For the photo it’s a little more complex. In order to send a photo to Telegram we need to ensure that it’s sent in a multipart format, and has the file type for Telegram.
Faraday is an http client which manages a whole bunch of things. In this case, it’s managing the wrapping. Incidentally if you want to send a file instead of a StringIO object the photo_stream variable should have filename and path of the file to be sent.
Finally, our caption parameter. This will be the text we received in our initial hash.
Final Note
I decided to write this piece, as I struggled to find documentation on how to send a photo as a variable. This missing piece was StringIO
which allows a string to be treated as a file.
Ten articles before and after
data-rh=”true”>Explainer: UK News Quiz, a Telegram Bot – Dror Kessler – Medium – Telegram Group
Telegram bot on the cloud. I wanted to create a bot in Telegram… – Telegram Group
Reacting to user feedback in a pet-project. Fast. – Telegram Group
XiongMai IP Camera Motion Detection Alert Snapshot to Telegram Bot – Telegram Group
On-The-Go Tools for Foreign Flow Analysis – Telegram Group
Telegram’da Benim de Bir Tuzum Olsun – Telegram Group
Controlling Light using Telegram Bot – Telegram Group
data-rh=”true”>Telegram Bot и отчётность по проекту – ToBe – Medium – Telegram Group
Делаем бота для учета личных расходов на Python используя Google Spreadsheets – Telegram Group