This library aims to make Telegram bots writing easy on the part of making sure all the bindings are available and up-to-date. It stems from the problem one has with (otherwise perfect) cl-telegram-bot—one has to add lots of methods to even make one’s bot ideas tested. This library solves the problem of having to write Telegram Bot API bindings by hand—all the classes and methods are generated automatically at load-time from telegram_api_json JSON files.
Git-cloning (recursively)
git clone --recursive https://github.com/aartaka/cl-telegram-bot-auto-api.git
# update the telegram_api_json submodule in case it's outdated
# (don't forget to open an issue if that's the case)
git submodule update -- telegram_api_json
and ASDF-loading
(asdf:load-system :cl-telegram-bot-auto-api)
should be enough to get started with this library, given that you have
- Dexador,
- Quri,
- NJSON,
- Bordeaux Threads,
- Alexandria,
- and Serapeum installed.
Let’s try making a simple echo bot, as in cl-telegram-bot README. Having a chat with BotFather and getting an access token is implied. Echo bot should respond to incoming messages with the same text they contain. We can use the tga:copy-mesage
for that just fine:
(defmethod tga:on ((message tga:message))
"Respond to the message with the same text it contains (actually using `tga:copy-message')."
(tga:copy-message (tga:id (tga:chat message)) (tga:id (tga:chat message)) (tga:message-id message)))
or we can rely on a lower-level things, like message
slots (note that you’d better use the accessors instead of slot-value
when dealing with cl-telegram-bot-auto-api
objects, because the accessors do lots of intuitive parsing of otherwise raw-ish objects these classes contain):
(defmethod tga:on ((message tga:message))
"Respond to the MESSAGE with the same text it contains."
(tga:send-message (tga:id (tga:chat message)) (tga:text message)))
As you can see, tga:on
is the main entry point for the (inherently event-driven) Telegram API (as implemented in this library). It’s on only restricted to message
processing, the update
slots (and eponymous classes) it processes are:
edited-message
,channel-post
,edited-channel-post
,inline-query
,chosen-inline-result
,callback-query
,shipping-query
,pre-checkout-query
,poll
,poll-answer
,chat-member
,my-chat-member
,chat-join-request
,bot-command
,- and others that
update
may contain in the future you’re loading this library in.
An additional use for on
that you may be interested in is… error handling. In case something goes wrong (error
-level wrong!), on
is called with the error instance in the handler-bind
context of this error. So, if you want to invoke some restarts or do something fancy with the error, on
is the place to do so! For example, if something goes wrong in our echo bot and is continuable (all the errors cl-telegram-auto-api generates are continuable, just in case), we can rest assured things will be fine with this method:
(defmethod tga:on ((object error))
"Invoke CONTINUE restart of OBJECT, if found."
(continue))
tga:start
is the ultimate entry point to launch your bot with. Simply pass the token and rock! (you can also pass the token and a :name
, so that the newly created bot thread is easier to find by name…)
(tga:start "YOUR-TOKEN" :name "My echo bot")
This:
- Creates a new thread.
- Binds
tga:*token*
to the one you provided for all the methods in this thread. - Calls
tga:get-updates
repetitively.- And then invokes either
:update-callback
ortga:on
with each of the fetched updates. - In case of errors, calls either
:error-callback
ortga:on
as the error handler.
- And then invokes either
The biggest goals/ideals for this library were:
- Not bothering with API bindings
- getting to writing the actual bot sooner, not having to care about up-to-date API bindings and contributing to someone else’s library.
- Being flexible to API changes
- no matter when you load the library (even if Telegram API has a version 103 by then), it should load just fine with all the available API methods, given that the JSON it’s parsed from is the same. I mean, that’s a lot of “if”-s, but much less that with the hand-written bindings that tend to go obsolete the moment they are published.
- Being flexible to one’s style
- This library is a terribly thin wrapper, so it is more likely to fit with your programming style than bigger and more opinionated libraries.
- In particular,
tga:on
, this universal processor for everything, may be totally ignored, if you providetga:start
with:update-callback
and:error-callback
arguments and do your work there. - You don’t need to define a class for every bot: simply call
tga:start
with different tokens, and it will spawn separate threads with bot-specific data. Then simplybt:destroy-thread
the ones you no longer need, and you’re done!
- In particular,
- Being image-based and lispy
- this library source code is not good for understanding what it does, because all the matter is hidden behind code-generating macros.
asdf:load-system
it,describe
the symbols you see, read thedocumentation
of the classes and functions it exposes. Use the facilities Lisp provides to interact with this library and understand what is there inside it.- While this library is implied for interactive REPL use, no one forbids you from compiling a binary calling
tga:start
in its entry point. See the “Being flexible to one’s style” point :)
- While this library is implied for interactive REPL use, no one forbids you from compiling a binary calling
Even though providing the full-blown library for immediate bot writing is explicitly not a goal, here are some small helpers that can ease your bot writing and are not likely to ever break, even with automated API generation:
- Passing objects to method by value
- It’s not cool to do
tga:id
,tga:update-id
,tga:message-id
every time you want totga:send-message
or do something else with several objects that you need to pass by ID. No more! Objects that have antga:id
method will be automatically turned into respective IDs when passed to methods that accept string/integer IDs instead of objects. So you can easily do:
(defmethod tga:on ((message tga:message))
(tga:copy-message (tga:chat message) ; No ID here.
(tga:chat message) ; And here!
message)) ; And here too!!!
tga:id
- This enables the previous point:
tga:id
applies to every object semantically having an ID (be itupdate-id
,message-id
etc. in Telegram) and returns the most sensible ID for it. No need to scour the docs for this-exact-slot-name-for-ID, just usetga:id
! tga:command
- Command parsing can be hard, especially when there are bot-mentioned commands and some complex text following them.
tga:command
(initially just a slot reader forbot-command
class) allows you to get the command name and the remaining text forupdate
ormessage
objects, just as a convenience for easy command parsing/dispatch. Shamelessly stolen from cl-telegram-bot as a feature worth having in every Telegram Bot API library!