[ANN] Telegram.jl - SDK, logger and bots

Hello everyone! I am really happy to announce Telegram.jl, a package that contains native Julia Telegram Messanger SDK, logging and bot facilities.

This package was built with the main idea of using Telegram as an instant message backend for various notification and reporting systems. So, the simplest way to use this package is by doing something like this

using Telegram, Telegram.API
tg = TelegramClient("YOUR TOKEN", chat_id = "YOUR CHAT_ID")

# Some lengthy calculations...
# ...

# After calculations complete
sendMessage(text  = "Calculation completed, result is $result")

and you shouldn’t worry and check regularly when your script finishes its work, you will see notification on your phone!

Let me briefly describe the main features of the package.

  1. One time setup for instant messaging.
    As you can see from the intro, if all you need is to send notification messages to single chat, you can set everything once at the beginning of the script
using Telegram, Telegram.API
tg = TelegramClient("YOUR TOKEN", chat_id = "YOUR CHAT_ID")

and all following Telegram.jl function calls will use these settings through all code, so the message sending looks like (of course, you can make everything explicit if needed).

sendMessage(text = "Hello world")
  1. Sending pictures, documents, and so on.
    You can easily send IO objects, they will be converted to a proper format
# Send image stored as a file
sendPhoto(photo = open("picture.jpg", "r"))

# Send in-memory generated object
iob = IOBuffer()
print(iob, "Hello world!")
sendDocument(document = "hello.txt" => iob)

which may come handy if you generated some visual reports as a result of calculations.

  1. Full cover of all Telegram bot functions.
    Well, this is just a small touch, but thanks to the combination of Gumbo.jl, Cascadia.jl and Underscores.jl all Telegram bot API functions were extracted from relevant web page and transformed in Julia functions. There is a fallback method apiquery which accepts method as a String, so you can call apiquery("getMe"), but I find getMe() more convenient.
    So, if you look at API Reference you would see more than 70 API methods with individual docstrings.

  2. Logging system
    Another usage of Telegram API is the logging system. Thanks to the awesome LoggingExtras.jl package, you only need to add couple of configuration lines and get telegram as an additional channel for critical messages.

using Telegram
using Logging, LoggingExtras

tg = TelegramClient(ENV["TG_TOKEN"], chat_id = ENV["TG_CHAT_ID"])
tg_logger = TelegramLogger(tg; async = false)
demux_logger = TeeLogger(
    MinLevelLogger(tg_logger, Logging.Error),
    ConsoleLogger()
)
global_logger(demux_logger)

@warn "It is bad"        # goes to console
@info "normal stuff"     # goes to console
@error "THE WORSE THING" # goes to console AND telegram
@debug "it is chill"     # goes to console

So if you have already a working application with established logging, you do not need to change anything in the main code to get telegram instant messaging.

  1. In addition, there is also a run_bot function, which makes it really easy to write Telegram bots. Here is an example of Echo bot, which just returns your message back
using Telegram, Telegram.API

TelegramClient(ENV["TG_TOKEN"])

run_bot() do msg
    sendMessage(text = msg.message.text, chat_id = msg.message.chat.id)
end

I’ve used this package to build a more advanced bot, which draws turtle graphics with the help of Luxor.jl package. As an input, it accepts a sequence of angles and then turtle draws an expanding spiral by turning on a defined angle each step. Code can be found in bot documentation and it uses one of the features, all drawings are generated in memory and immediately sent to the Telegram without storing in the filesystem.

You can find this bot here: Telegram: Contact @julia_turtle_experiment_bot and it’s still running without any issues after it was launched a few days ago.

P.S.: Development of this package was sponsored by a secret organization which prefer to remain anonymous, but you can find it’s logo by entering following coordinates in turtle bot: 30 30 179.02

47 Likes

amazing! thanks for writing this.

3 Likes

This is brilliant!

Telegram has good github integration too (alerts when issues posted etc), so we’ll soon be able to let the bots do all our work for us, and they can talk to each other in geometrical.constructions… :slight_smile:

4 Likes

Very cool :clap::clap::clap:

4 Likes

Wow, this is super easy to use. I am very happy to use such a nicely done package

3 Likes

Uhh really cool, that would have come really handy a couple weeks ago when I was looking for apartments and wrote an email bot. This would have been much easier and nicer!

2 Likes

Wow. I’ve been using similar packages in python and had been searching for something like this in Julia. Great work.

The API is mentioned everywhere in the package and the documentation as “Telegram API”, but from what I understand this package implements the “Telegram Bot API”. The Telegram Bot API is an API specifically for bots, which is simpler but less customisable and acts as an intermediary between bots and the Telegram API. The two APIs are described here. It might be worth mentioning to avoid confusion. An example for both of these implemented in python would be, python-telegram-bot for Telegram Bot API and Telethon for Telegram API.

2 Likes

Thank you, you are right. I’ve made necessary changes to documentation.

3 Likes

Thank you very much for this awesome package, you have changed my life as an user who runs long computations ^^

1 Like

Hello @Skoffer . For some reason, the following simple echo code stopped working:

using Telegram, Telegram.API

token = "xxx"

tg = TelegramClient(token)

foo(msg) = sendMessage(text = msg.message.text, chat_id = msg.message.chat.id)

run_bot(foo, tg)

It results in

Error: KeyError(:message)
â”” @ Telegram ~/.julia/packages/Telegram/gsaco/src/bot.jl:34
┌ Error: UndefVarError(:ignore_errors)
â”” @ Telegram ~/.julia/packages/Telegram/gsaco/src/bot.jl:43

Do you know what can be wrong?

Thank you.

Surprisingly, to make it work I have to run it the first time with an empty function:

function foo(msg)
    return nothing
end

Telegram.jl is a low-level library, so it does not make any preprocessing of input data. It means, that msg is a raw object which was received from Telegram and it is the developer’s responsibility to process it correctly. Tutorial is overly simplified, to keep essential things clear, but in a real world, more layers of data validation should be added of course.

In your particular case, there is no :message field in msg for some reason. There are multiple scenarios, why it has happened, maybe someone send emoji instead of text or may be there was some other special message, it’s hard to tell. You can avoid it in multiple ways, for example you can use try/catch block, like this

function foo(msg)
  try
    sendMessage(text = msg.message.text, chat_id = msg.message.chat.id)
  catch e
    @error e
  end
end

Or you can try to parse it

function msgdata(msg)
  message = get(msg, :message, nothing)
  message === nothing && return nothing, nothing
  text = get(message, :text, nothing)
  chat = get(message, :chat, nothing)
  chat === nothing && return text, nothing
  chat_id = get(chat, :id, nothing)
  return text, chat_id
end

function foo(msg)
   text, chat_id = msgdata(msg)
   if text !== nothing && chat_id !== nothing
     sendMessage(text = text, chat_id = chat_id)
   end
end

Regarding, your empty foo example, it just consumed “wrong” message and clear the message queue for proper messages.

1 Like

Thank you very much for the detailed explanation.

Nice package!

I have one question. How can you send a plot without creating a file on disk first?

I tried this:

using GLMakie, Telegram

tg = Telegram.TelegramClient("token", chat_id = "id") # token , id hidden here for privacy

x = 0:0.01:Ď€
plt = lines(x, sin.(x))

io = IOBuffer()
show(io, MIME"image/png"(), plt)

Telegram.API.sendPhoto(tg, photo=io)

But this gives me an HTTP Bad request error. The following picture is truncated to hide the token.
Any suggestions?

image

I found a way to send the plot as an attachment:

using GLMakie, Telegram

tg = Telegram.TelegramClient("token", chat_id = "id") # token , id hidden here for privacy

x = 0:0.01:Ď€
plt = lines(x, sin.(x))
let io = IOBuffer()
	show(io, MIME"image/png"(), plt);
	Telegram.API.sendDocument(tg, document = ("plot.png" => io))
end

Though it would be nicer if I can send it as a photo, which is displayed within telegram.

You can see all available methods here: Telegram Bot API They are in one-to-one correspondence with Telegram.API methods.

In particular, to send image as photo, you can use Telegram Bot API

using GLMakie, Telegram

tg = Telegram.TelegramClient("token", chat_id = "id") # token , id hidden here for privacy

x = 0:0.01:Ď€
plt = lines(x, sin.(x))
let io = IOBuffer()
	show(io, MIME"image/png"(), plt);
	Telegram.API.sendPhoto(tg, photo = ("plot.png" => io))
end
1 Like

Thanks!