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_jsonand 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
updatemay 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-updatesrepetitively.- And then invokes either
:update-callbackortga:onwith each of the fetched updates. - In case of errors, calls either
:error-callbackortga:onas 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:startwith:update-callbackand:error-callbackarguments and do your work there. - You don’t need to define a class for every bot: simply call
tga:startwith different tokens, and it will spawn separate threads with bot-specific data. Then simplybt:destroy-threadthe 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-systemit,describethe symbols you see, read thedocumentationof 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:startin 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-idevery time you want totga:send-messageor do something else with several objects that you need to pass by ID. No more! Objects that have antga:idmethod 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:idapplies to every object semantically having an ID (be itupdate-id,message-idetc. 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-commandclass) allows you to get the command name and the remaining text forupdateormessageobjects, 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!