Usage

Setting up telegram token and chat_id

In all examples of this section, it is assumed for simplicity that you set telegram token and chat_id in TG_TOKEN and TG_CHAT_ID environment variables correspondingly. So you can run your julia session like this for example

sh> export TG_TOKEN=123456:ababababababbabababababbababaab
sh> export TG_CHAT_ID=1234567
sh> julia

In order to get token itself, you should follow this instruction. Just talk to BotFather and after few simple questions you will receive the token.

Easiest way to obtain chat_id is through running simple print bot.

using Telegram

tg = TelegramClient(ENV["TG_TOKEN"])

run_bot() do msg
    println(msg)
end

After running this script just open chat with your newly created bot and send it any message. You will receive something like this

{
   "update_id": 87654321,
     "message": {
                   "message_id": 6789,
                         "from": {
                                               "id": 123456789,
                                           "is_bot": false,
                                    "language_code": "en"
                                 },
                         "chat": {
                                            "id": 123456789,
                                          "type": "private"
                                 },
                         "date": 1592767594,
                         "text": "Hello"
                }
}

In this example, field message.chat.id = 123456789 is necessary chat_id which shoud be stored in TG_CHAT_ID variable.

Initializing TelegramClient

To initialize client you need to pass reuired token parameter.

using Telegram

tg = TelegramClient(ENV["TG_TOKEN"])

Since Telegram.jl was built with the first-class support of the Telegram as a notification system, you can pass chat_id variable, which will be used then in every function related to messaging

using Telegram

tg = TelegramClient(ENV["TG_TOKEN"]; chat_id = ENV["TG_CHAT_ID"])

Telegram.sendMessage(tg, text = "Hello world") # will send "Hello world" message 
                                               # to chat defined in ENV["TG_CHAT_ID"] variable

Also, by default new TelegramClient is used globally in all API Reference related functions, so you can run commands like

using Telegram

tg = TelegramClient(ENV["TG_TOKEN"]; chat_id = ENV["TG_CHAT_ID"])
Telegram.sendMessage(text = "Hello world")

which will send "Hello world" message to the chat defined by ENV["TG_CHAT_ID"] variable with the bot defined by ENV["TG_TOKEN"] variable.

In order to override this behaviour you can set use_globally argument of TelegramClient function. To set previously defined client as a global, you should use useglobally!.

Using Telegram Bot API

Due to the rather large number of functions defined in API Reference, they are hidden behind module declaration, so by default they should be prefixed with Telegram.

using Telegram

TelegramClient(ENV["TG_TOKEN"])

Telegram.getMe() # returns information about bot

If this is inconvenient for some reason, you can either introduce new and short constant name, like this

using Telegram
const TG = Telegram

TelegramClient(ENV["TG_TOKEN"])

TG.getMe()

or you can import all telegram Bot API by using Telegram.API, in this scenario you do not need to use any prefixes

using Telegram
using Telegram.API

TelegramClient(ENV["TG_TOKEN"])

getMe()

In what follows we will use latter approach.

Sending messages

If you set telegram client globally with chat_id as it is described in previous sections, then you can use message related function from API Reference, omitting chat_id argument. For example, this is how you can use most basic sendMessage function

using Telegram, Telegram.API

TelegramClient(ENV["TG_TOKEN"], chat_id = ENV["TG_CHAT_ID"])

sendMessage(text = "Hello world")

Of course if you have more than one client or writing a bot which should communicate in multiple chats, you can add this parameters to function calls and they will override default values, for example

using Telegram, Telegram.API

tg1 = TelegramClient(ENV["TG_TOKEN"]; chat_id = ENV["TG_CHAT_ID"])
tg2 = TelegramClient(ENV["TG_TOKEN2"])

sendMessage(tg1, text = "I am bot number 1", chat_id = 12345)
sendMessage(tg2, text = "I am bot number 2", chat_id = 54321)

will send messages from two different telegram bots to two different chats. It is useful for example, when you have telegram bot communicating with users and at the same time error logs of this bot is being sent to another chat by error reporting bot.

In addition to text messages you can also send any sort of IO objects: images, audio, documents and so on. For example to send picture you can do something like this

using Telegram, Telegram.API

TelegramClient(ENV["TG_TOKEN"], chat_id = ENV["TG_CHAT_ID"])

open("picture.jpg", "r") do io
    sendPhoto(photo = io)
end

# or if you want it quick and dirty
sendPhoto(photo = open("picture.jpg", "r"))

Data sending is not limited by files only, you can send memory objects as well, in this case you should give them name in the form of Pair

using Telegram, Telegram.API

TelegramClient(ENV["TG_TOKEN"], chat_id = ENV["TG_CHAT_ID"])

io = IOBuffer()
print(io, "Hello world!")
sendDocument(document = "hello.txt" => io)

Logging

You can also use Telegram.jl as a logging system, for this you are provided with special TelegramLogger structure. It accepts TelegramClient object which must have initialized chat_id parameter

using Telegram
using Logging

tg = TelegramClient(ENV["TG_TOKEN"], chat_id = ENV["TG_CHAT_ID"])
tg_logger = TelegramLogger(tg; async = false)

with_logger(tg_logger) do
    @info "Hello from telegram logger!"
end

But even better it is used together with LoggingExtras.jl package, which can demux log messages and send critical messages to telegram backend without interrupting normal logging flow

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

This way, just by adding few configuration lines, you can have unchanged logging system with telegram instant messaging if anything critically important happened.

Also, take notion of async argument of TelegramLogger. There are two modes of operating, usual and asynchronous. Second is useful if you have long running program and you do not want it to pause and send telegram message, so usual scenario is like this

while true
    try
        # do some stuff
    catch err
        @error err
        # process error
    end
end    

But asynchronous mode has it's drawbacks, consider this for example

try
    sqrt(:a)
catch err
    @error err
end

if you run this snippet (with proper logger initialization) from command line, main thread stops before asynchronous message to telegram is sent. In such cases, it make sense to set async = false

Bots

Echo bot

With the help of run_bot method it's quite simple to set up simple telegram bots.

using Telegram, Telegram.API

token = ENV["TG_TOKEN"]
tg = TelegramClient(token)

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

Turtle graphics bot

In this example we build more advanced bot, which is generating turtle graphics with the help of Luxor.jl package.

In addition to previous echo bot, this can do the following

  1. Generate and send images in memory, without storing them in file system
  2. Generate virtual keyboard, which can be used by users to make input easier
using Telegram, Telegram.API
using Luxor

token = ENV["TG_TOKEN"]
tg = TelegramClient(token)

"""
    draw_turtle(angles::AbstractVector)
    
Draw turtle graphics, where turtle is moving in spiral, on each step rotating
on next angle from `angles` vector. Vector `angles` is repeated cyclically.
"""
function draw_turtle(angles)
    d = Drawing(600, 400, :png)
    origin()
    background("midnightblue")

    ๐Ÿข = Turtle() # you can type the turtle emoji with \:turtle:
    Pencolor(๐Ÿข, "cyan")
    Penwidth(๐Ÿข, 1.5)
    n = 5.0
    dn = 1.0/length(angles)*0.7
    for i in 1:400
        for angle in angles
            Forward(๐Ÿข, n)
            Turn(๐Ÿข, angle)
            n += dn
        end
        HueShift(๐Ÿข)
    end
    finish()

    return d
end

"""
    build_keyboard()
    
Generates [telegram keyboard](https://core.telegram.org/bots#keyboards) in the
form of 3x3 grid of buttons.
"""
function build_keyboard()
    keyboard = Vector{Vector{String}}()
    for x in 1:3
        row = String[]
        for y in 1:3
            s = join(string.(Int.(round.(rand(rand(1:4)) * 360))), " ")
            push!(row, s)
        end
        push!(keyboard, row)
    end

    return Dict(:keyboard => keyboard, :one_time_keyboard => true)
end

run_bot() do msg
    message = get(msg, :message, nothing)
    message === nothing && return nothing
    text = get(message, :text, "")
    chat = get(message, :chat, nothing)
    chat === nothing && return nothing
    chat_id = get(chat, :id, nothing)
    chat_id === nothing && return nothing
    
    if match(r"^[0-9 \.]+$", text) !== nothing
        angles = parse.(Float64, split(text, " "))
        turtle = draw_turtle(angles)
        sendPhoto(photo = "turtle.png" => turtle.buffer, reply_markup = build_keyboard(), chat_id = chat_id)
    else
        sendMessage(text = "Unknown command, please provide turtle instructions in the form `angle1 angle2` or use keyboard", reply_markup = build_keyboard(), chat_id = chat_id, parse_mode = "MarkdownV2")
    end
end