diff --git a/ChangeLog.md b/ChangeLog.md
index 53c9ad5..62ef093 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -2,12 +2,42 @@
# ChangeLog
+
+
+## 0.5.0 (2024-02-18)
+
+
+
+### Added
+
+* Now bot can be started in debug mode. When this mode is on, then interactive debugger will pop up on errors.
+* If bot defines some commands implementing [`cl-telegram-bot/entities/command:on-command`][56c0] generic-function, then
+ these commands will be reported to the telegram server automatically and it will show the to user when he
+ starts text with `/`.
+* Added support for buttons with callbacks. To define a callback, implement a method for
+ [`cl-telegram-bot/callback:on-callback`][1b93] generic-function. After that, you can construct an inline keyboard
+ using [`cl-telegram-bot/inline-keyboard:inline-keyboard`][35e0] function and [`cl-telegram-bot/inline-keyboard:callback-button`][734b] function.
+ This keyboard object can be supplied as `:REPLY-MARKUP` argument to [`cl-telegram-bot/response:reply`][0d9a] function.
+* New functions `cl-telegram-bot/response:alert` ([`1`][61ac] [`2`][4b97]) and `cl-telegram-bot/response:notify` ([`1`][6672] [`2`][0817]) were added. An example usage of these functions
+ along with inline keyboard was added to `example/bot.lisp`.
+* Function [`cl-telegram-bot/response-processing:interrupt-processing`][e96a] was added in case if you want to interrupt processing of
+ current message and skip the rest of the handler.
+* Function [`cl-telegram-bot/message:get-current-message`][4af2] was added.
+* Function [`cl-telegram-bot/message:get-current-chat`][e428] was added.
+
+
+
+### Removed
+
+* Function `CL-TELEGRAM-BOT/MESSAGE:REPLY` was removed and replaced with [`cl-telegram-bot/response:reply`][0d9a] function.
+ Previously it interrupted the processing flow and you only was able to reply once. With the new function
+ you can respond with different pieces, for example to show user a image and text with inline keyboard.
+
## 0.4.0 (2023-04-22)
* Changed a lot of imports some symbols not bound to functions were removed, some readers and accessors are exported.
-
* Added an autogenerated `API` reference.
@@ -15,13 +45,9 @@
## 0.3.2 (2022-07-10)
* Change the parameters of `make-request` to allow passing request parameters straight into it.
-
* Add `id` slot to `message`; add `forward-message`, `delete-message` functions reliant on `id`.
-
* Add more `message` types: `reply`, `video-message`, `document-message` and other media message types.
-
* Add `send-*` media-sending message types.
-
* Add more `chat` types: `group`, `supergroup`, and `channel`.
@@ -43,9 +69,7 @@
* Added a dependency from `trivial-timeout` and now connect timeout is used when
doing requests to `API`.
-
* Function `make-` now proxie any parameters to the class's constructor.
-
* Now function `stop-processing` checks if thread is alive before destroying it.
@@ -61,5 +85,18 @@ Project was broken down to subpackages, nicknames `telegram-bot` and
package-inferred-system class and each file have it's own separate packages.
+[1b93]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FCALLBACK-3AON-CALLBACK-20GENERIC-FUNCTION-29
+[56c0]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FENTITIES-2FCOMMAND-3AON-COMMAND-20GENERIC-FUNCTION-29
+[734b]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FINLINE-KEYBOARD-3ACALLBACK-BUTTON-20FUNCTION-29
+[35e0]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FINLINE-KEYBOARD-3AINLINE-KEYBOARD-20FUNCTION-29
+[e428]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FMESSAGE-3AGET-CURRENT-CHAT-20FUNCTION-29
+[4af2]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FMESSAGE-3AGET-CURRENT-MESSAGE-20FUNCTION-29
+[4b97]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FRESPONSE-3AALERT-20CLASS-29
+[61ac]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FRESPONSE-3AALERT-20FUNCTION-29
+[0817]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FRESPONSE-3ANOTIFY-20CLASS-29
+[6672]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FRESPONSE-3ANOTIFY-20FUNCTION-29
+[0d9a]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FRESPONSE-3AREPLY-20FUNCTION-29
+[e96a]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FRESPONSE-PROCESSING-3AINTERRUPT-PROCESSING-20FUNCTION-29
+
* * *
###### [generated by [40ANTS-DOC](https://40ants.com/doc/)]
diff --git a/README.md b/README.md
index 2b340c3..d892023 100644
--- a/README.md
+++ b/README.md
@@ -6,21 +6,13 @@
## CL-TELEGRAM-BOT ASDF System Details
-* Version: 0.4.0
-
* Description: Telegram Bot `API`, based on sovietspaceship's work but mostly rewritten.
-
* Licence: `MIT`
-
* Author: Alexander Artemenko
-
* Homepage: [https://40ants.com/cl-telegram-bot/][6949]
-
* Bug tracker: [https://github.com/40ants/cl-telegram-bot/issues][5798]
-
* Source control: [GIT][53d1]
-
-* Depends on: [alexandria][8236], [arrows][b590], [bordeaux-threads][3dbf], [cl-ppcre][49b9], [cl-strings][2ecb], [closer-mop][61a4], [dexador][8347], [jonathan][6dd8], [kebab][5186], [log4cl][7f8b], [serapeum][c41d], [trivial-backtrace][fc0e]
+* Depends on: [alexandria][8236], [arrows][b590], [bordeaux-threads][3dbf], [cl-ppcre][49b9], [cl-strings][2ecb], [closer-mop][61a4], [dexador][8347], [jonathan][6dd8], [kebab][5186], [log4cl][7f8b], [serapeum][c41d], [str][ef7f], [trivial-backtrace][fc0e]
[][7bb5]
@@ -105,764 +97,1153 @@ And start communicating with him:
## API
-
+
-### CL-TELEGRAM-BOT/MESSAGE
+### CL-TELEGRAM-BOT/BOT
-
+
-#### [package](287f) `cl-telegram-bot/message`
+#### [package](c186) `cl-telegram-bot/bot`
-
+
#### Classes
-
+
-##### ANIMATION-MESSAGE
+##### BOT
-
+
-###### [class](eef7) `animation-message` (file-message)
+###### [class](0c3b) `bot` ()
-
+**Readers**
-##### ANIMATION
+
-
+###### [reader](3953) `api-uri` (bot) (:API-URI = "https://api.telegram.org/")
-###### [class](7aad) `animation` (file temporal spatial)
+
-
+###### [reader](86e0) `debug-mode` (bot) (:debug-mode = nil)
-##### AUDIO-MESSAGE
+When debug mode is T, then interactive debugger will be called on each error.
-
+
-###### [class](19f6) `audio-message` (file-message)
+###### [reader](37ea) `file-endpoint` (bot) (:file-endpoint = nil)
-
+`HTTPS` file-endpoint
-##### AUDIO
+
-
+###### [reader](4476) `get-endpoint` (bot) (:endpoint)
-###### [class](9d6d) `audio` (file temporal)
+`HTTPS` endpoint
-**Readers**
+
-
+###### [reader](e7ba) `get-last-update-id` (bot) (= 0)
-###### [reader](e514) `get-performer` (audio) (:performer)
+Update id
-Performer of the audio as defined by sender or by audio tags.
+
-
+###### [reader](30e6) `sent-commands-cache` (bot) (= nil)
-###### [reader](5257) `get-title` (audio) (:title)
+Command processing code will use this cache to update commands list on the server
+when a new method for [`cl-telegram-bot/entities/command:on-command`][56c0] generic-function is defined.
-Title of the audio as defined by sender or by audio tags.
+This slot is for internal use.
-
+
-##### DOCUMENT-MESSAGE
+###### [reader](419d) `token` (bot) (:token = nil)
-
+Bot token given by BotFather
-###### [class](0418) `document-message` (file-message)
+**Accessors**
-
+
-##### DOCUMENT
+###### [accessor](3953) `api-uri` (bot) (:API-URI = "https://api.telegram.org/")
-
+
-###### [class](1141) `document` (file)
+###### [accessor](86e0) `debug-mode` (bot) (:debug-mode = nil)
-
+When debug mode is T, then interactive debugger will be called on each error.
-##### FILE-MESSAGE
+
-
+###### [accessor](37ea) `file-endpoint` (bot) (:file-endpoint = nil)
-###### [class](799d) `file-message` (message)
+`HTTPS` file-endpoint
-**Readers**
+
-
+###### [accessor](e7ba) `get-last-update-id` (bot) (= 0)
-###### [reader](b2c7) `get-file` (file-message) (:file)
+Update id
-
+
-##### FILE
+###### [accessor](30e6) `sent-commands-cache` (bot) (= nil)
-
+Command processing code will use this cache to update commands list on the server
+when a new method for [`cl-telegram-bot/entities/command:on-command`][56c0] generic-function is defined.
-###### [class](1539) `file` ()
+This slot is for internal use.
-**Readers**
+
-
+###### [accessor](419d) `token` (bot) (:token = nil)
-###### [reader](86bb) `get-file-id` (file) (:file-id)
+Bot token given by BotFather
-Identifier for this file, which can be used to download or reuse the file.
+
-
+#### Macros
-###### [reader](1559) `get-file-name` (file) (:file-name)
+
-Original filename as defined by sender.
+##### [macro](cce0) `defbot` name
-
+
-###### [reader](2b03) `get-file-size` (file) (:file-size)
+### CL-TELEGRAM-BOT/CALLBACK
-File size in bytes.
+
-
+#### [package](96f5) `cl-telegram-bot/callback`
-###### [reader](00ba) `get-file-unique-id` (file) (:file-unique-id)
+
-Unique identifier for this file, which is supposed to be the same
-over time and for different bots. Can't be used to download or reuse
-the file.
+#### Classes
-
+
-###### [reader](29cf) `get-mime-type` (file) (:mime-type)
+##### CALLBACK
-`MIME` type of the file as defined by sender.
+
-
+###### [class](ea25) `callback` ()
-##### MESSAGE
+**Readers**
-
+
-###### [class](5c0b) `message` ()
+###### [reader](322a) `callback-data` (callback) (:data)
-**Readers**
+
-
+###### [reader](7a40) `callback-id` (callback) (:id)
-###### [reader](7b24) `get-caption` (message) (:caption)
+
-Caption for the animation, audio, document, photo, video or voice.
+###### [reader](ada7) `callback-message` (callback) (:message)
-
+
-###### [reader](f214) `get-chat` (message) (:chat)
+#### Generics
-
+
-###### [reader](38bc) `get-entities` (message) (:entities = nil)
+##### [generic-function](e9df) `callback-chat` callback
-
+Returns a chat from where callback was sent.
-###### [reader](f765) `get-forward-from` (message) (:forward-from)
+
-For forwarded messages, sender of the original message.
+##### [generic-function](a9c2) `make-callback` bot callback-data
-
+Called when user clicks callback button. Should return an instance of [`callback`][6611] class.
-###### [reader](5ea8) `get-forward-from-chat` (message) (:forward-from-chat)
+Application may override this method to return objects of different callback classes depending on
+callback-data string. This way it mab be easier to define more specific methods for
+[`on-callback`][1b93] generic-function.
-For messages forwarded from channels or from anonymous
-administrators, information about the original sender chat.
+
-
+##### [generic-function](7289) `on-callback` bot callback
-###### [reader](d8cb) `get-forward-sender-name` (message) (:forward-sender-name)
+Called when user clicks callback button. Second argument is an object of `CALLBACK` type.
-For forwarded messages, sender of the original message.
+
-
+### CL-TELEGRAM-BOT/CHAT
-###### [reader](42cb) `get-message-id` (message) (:id)
+
-
+#### [package](3a02) `cl-telegram-bot/chat`
-###### [reader](15b9) `get-raw-data` (message) (:raw-data)
+
-
+#### Classes
-###### [reader](7c01) `get-text` (message) (:text)
+
-
+##### CHANNEL
-##### PHOTO-MESSAGE
+
-
+###### [class](0a0d) `channel` (base-group)
-###### [class](4f9d) `photo-message` (file-message)
+
-**Readers**
+##### CHAT
-
+
-###### [reader](414a) `get-photo-options` (photo-message) (:photo-options)
+###### [class](149d) `chat` ()
-
+**Readers**
-##### PHOTO
+
-
+###### [reader](ebda) `get-chat-id` (chat) (:id)
-###### [class](d514) `photo` (file spatial)
+
-
+###### [reader](b233) `get-has-protected-content` (chat) (:has-protected-content)
-##### REPLY
+
-
+###### [reader](38fa) `get-message-auto-delete-time` (chat) (:message-auto-delete-time)
-###### [class](2a1a) `reply` (message)
+
-**Readers**
+###### [reader](622d) `get-raw-data` (chat) (:raw-data)
-
+
-###### [reader](2cce) `cl-telegram-bot/message:get-reply-to-message` (reply) (:reply-to-message)
+###### [reader](6f83) `get-username` (chat) (:username)
-
+
-##### SPATIAL
+##### GROUP
-
+
-###### [class](e6a1) `spatial` ()
+###### [class](e783) `group` (base-group)
-**Readers**
+
-
+##### PRIVATE-CHAT
-###### [reader](1437) `get-height` (spatial) (:height)
+
-File height as defined by sender.
+###### [class](4fc7) `private-chat` (chat)
-
+**Readers**
-###### [reader](db4a) `get-width` (spatial) (:width)
+
-File width as defined by sender.
+###### [reader](b76c) `get-bio` (private-chat) (:bio)
-
+
-##### STICKER-MESSAGE
+###### [reader](6b6d) `get-first-name` (private-chat) (:first-name)
-
+
-###### [class](6759) `sticker-message` (file-message)
+###### [reader](1a8d) `get-has-private-forwards` (private-chat) (:has-private-forwards)
-
+
-##### STICKER
+###### [reader](8c65) `get-last-name` (private-chat) (:last-name)
-
+
-###### [class](cfb8) `sticker` (file spatial)
+##### SUPER-GROUP
+
+
+
+###### [class](e8be) `super-group` (base-group)
**Readers**
-
+
-###### [reader](d1ce) `get-emoji` (sticker) (:emoji)
+###### [reader](2807) `get-can-set-sticker-set` (super-group) (:can-set-sticker-set)
-Emoji associated with the sticker
+
-
+###### [reader](79c0) `get-join-by-request` (super-group) (:join-by-request)
-###### [reader](5da1) `get-is-animated` (sticker) (:is-animated)
+
-True if the sticker is animated.
+###### [reader](8d78) `get-join-to-send-messages` (super-group) (:join-to-send-messages)
-
+
-###### [reader](f513) `get-is-video` (sticker) (:is-video)
+###### [reader](ddc3) `get-slow-mode-delay` (super-group) (:slow-mode-delay)
-True if the sticker is a video sticker.
+
-
+###### [reader](315b) `get-sticker-set-name` (super-group) (:sticker-set-name)
-###### [reader](6f47) `get-set-name` (sticker) (:set-name)
+
-Name of the sticker set to which the sticker belongs.
+#### Functions
-
+
-##### TEMPORAL
+##### [function](e8ed) `delete-chat-photo` bot-var1 chat
-
+https://core.telegram.org/bots/api#deletechatphoto
-###### [class](1326) `temporal` ()
+
-**Readers**
+##### [function](aa79) `export-chat-invite-link` bot-var1 chat
-
+https://core.telegram.org/bots/api#exportchatinvitelink
-###### [reader](ecd6) `get-duration` (temporal) (:duration)
+
-Duration of the file in seconds as defined by sender.
+##### [function](905e) `get-chat-administrators` bot-var1 chat
-
+https://core.telegram.org/bots/api#getchatadministrators
-##### UNISPATIAL
+
-
+##### [function](0b6d) `get-chat-by-id` bot-var1 chat-id
-###### [class](5561) `unispatial` ()
+https://core.telegram.org/bots/api#getchat
-**Readers**
+
-
+##### [function](469a) `get-chat-member` bot-var1 chat user-id
-###### [reader](9dce) `get-length` (unispatial) (:length)
+https://core.telegram.org/bots/api#getchatmember
-
+
-##### VIDEO-MESSAGE
+##### [function](bdd6) `get-chat-members-count` bot-var1 chat
-
+https://core.telegram.org/bots/api#getchatmemberscount
-###### [class](4c67) `video-message` (file-message)
+
-
+##### [function](f22b) `kick-chat-member` bot-var1 chat user-id until-date
-##### VIDEO-NOTE-MESSAGE
+https://core.telegram.org/bots/api#kickchatmember
-
+
-###### [class](5e1e) `video-note-message` (file-message)
+##### [function](7d1f) `leave-chat` bot-var1 chat
-
+https://core.telegram.org/bots/api#leavechat
-##### VIDEO-NOTE
+
-
+##### [function](0e8e) `pin-chat-message` bot-var1 chat message-id disable-notification
-###### [class](6c67) `video-note` (file temporal unispatial)
+https://core.telegram.org/bots/api#pinchatmessage
-
+
-##### VIDEO
+##### [function](facd) `promote-chat-member` bot-var1 chat user-id can-change-info can-post-messages can-edit-messages can-delete-messages can-invite-users can-restrict-members can-pin-messages can-promote-members
-
+https://core.telegram.org/bots/api#promotechatmember
-###### [class](1593) `video` (file temporal spatial)
+
-
+##### [function](139f) `restrict-chat-member` bot-var1 chat user-id until-date can-send-messages can-send-media-messages can-send-other-messages can-add-web-page-previews
-##### VOICE-MESSAGE
+https://core.telegram.org/bots/api#restrictchatmember
-
+
-###### [class](6533) `voice-message` (file-message)
+##### [function](11f4) `send-chat-action` bot-var1 chat action
-
+https://core.telegram.org/bots/api#sendchataction
-##### VOICE
+
-
+##### [function](1403) `set-chat-description` bot-var1 chat description
-###### [class](99ec) `voice` (file temporal)
+https://core.telegram.org/bots/api#setchatdescription
-
+
-#### Generics
+##### [function](80ea) `set-chat-photo` bot-var1 chat photo
-
+https://core.telegram.org/bots/api#setchatphoto
-##### [generic-function](9364) `on-message` bot text
+
-This method gets called with raw text from the message.
-By default it does nothing.
+##### [function](32e3) `set-chat-title` bot-var1 chat title
-
+https://core.telegram.org/bots/api#setchattitle
-##### [generic-function](523c) `send-animation` bot chat animation &rest options &key caption parse-mode caption-entities duration width height thumb disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
+
-Sends animation to a chat.
+##### [function](8eec) `unban-chat-member` bot-var1 chat user-id
-
+https://core.telegram.org/bots/api#unbanchatmember
-##### [generic-function](f0a0) `send-audio` bot chat audio &rest options &key caption parse-mode caption-entities duration performer title thumb disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
+
-
+##### [function](0e36) `unpin-chat-message` bot-var1 chat
-##### [generic-function](ed33) `send-document` bot chat document &rest options &key caption parse-mode caption-entities disable-content-type-detection thumb disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
+https://core.telegram.org/bots/api#unpinchatmessage
-
+
-##### [generic-function](6344) `send-photo` bot chat photo &rest options &key caption parse-mode caption-entities disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
+### CL-TELEGRAM-BOT/CORE
-
+
-##### [generic-function](524d) `send-sticker` bot chat sticker &rest options &key disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
+#### [package](6b7a) `cl-telegram-bot/core`
-A function to send sticker.
+
-
+#### Classes
-##### [generic-function](af9c) `send-video` bot chat video &rest options &key caption parse-mode caption-entities duration width height thumb disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
+
-
+##### REPLY
-##### [generic-function](6106) `send-video-note` bot chat video-note &rest options &key caption parse-mode caption-entities duration length thumb disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
+
-
+###### [class](93c4) `reply` (response-with-text)
-##### [generic-function](261c) `send-voice` bot chat voice &rest options &key caption parse-mode caption-entities duration disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
+
-
+#### Generics
+
+
+
+##### [generic-function](620e) `on-command` bot command rest-text
+
+This method will be called for each command.
+First argument is a keyword. If user input was /save_note, then
+first argument will be :save-note.
+
+By default, logs call and does nothing.
+
+
+
+##### [generic-function](0a3f) `on-message` bot text
+
+This method gets called with raw text from the message.
+By default it does nothing.
+
+
#### Functions
-
+
-##### [function](6059) `delete-message` bot chat message
+##### [function](0029) `reply` text &rest args &key parse-mode disable-web-page-preview disable-notification reply-to-message-id reply-markup (immediately t)
-https://core.telegram.org/bots/api#deletemessage
+Works like a [`send-message`][38a1], but only when an incoming message is processed.
+Automatically sends reply to a chat from where current message came from.
-
+
-##### [function](7691) `forward-message` bot chat from-chat message &key disable-notification
+##### [function](2385) `start-processing` BOT &KEY DEBUG (DELAY-BETWEEN-RETRIES 10) (THREAD-NAME "telegram-bot")
-https://core.telegram.org/bots/api#forwardmessage
+
-
+##### [function](e95e) `stop-processing` bot
-##### [function](3c79) `get-current-chat`
+
-Returns a chat where currently processing message was received.
+#### Macros
-
+
-##### [function](5191) `make-message` data
+##### [macro](cce0) `defbot` name
-
+
-##### [function](9c91) `reply` text &rest args &key parse-mode disable-web-page-preview disable-notification reply-to-message-id reply-markup
+### CL-TELEGRAM-BOT/ENTITIES/COMMAND
-Works like a send-message, but only when an incoming message is processed.
-Automatically sends reply to a chat from where current message came from.
+
-
+#### [package](769c) `cl-telegram-bot/entities/command`
-##### [function](aec0) `send-message` bot chat text &rest options &key parse-mode disable-web-page-preview disable-notification reply-to-message-id reply-markup
+
-https://core.telegram.org/bots/api#sendmessage
+#### Classes
-
+
-### CL-TELEGRAM-BOT/TELEGRAM-CALL
+##### BOT-COMMAND
-
+
-#### [package](32dc) `cl-telegram-bot/telegram-call`
+###### [class](3367) `bot-command` (entity)
-
+**Readers**
-### CL-TELEGRAM-BOT/CHAT
+
-
+###### [reader](bd97) `get-command` (bot-command) (:command)
-#### [package](48f6) `cl-telegram-bot/chat`
+
-
+###### [reader](f5bd) `get-rest-text` (bot-command) (:rest-text)
+
+
+
+#### Generics
+
+
+
+##### [generic-function](620e) `on-command` bot command rest-text
+
+This method will be called for each command.
+First argument is a keyword. If user input was /save_note, then
+first argument will be :save-note.
+
+By default, logs call and does nothing.
+
+
+
+### CL-TELEGRAM-BOT/ENTITIES/CORE
+
+
+
+#### [package](e8b5) `cl-telegram-bot/entities/core`
+
+
+
+#### Generics
+
+
+
+##### [generic-function](6a05) `make-entity-internal` entity-type payload data
+
+Extendable protocol to support entities of different kinds.
+First argument is a keyword, denoting a type of the entity.
+Payload is an object of type `message'.
+And data is a plist with data, describing the entity.
+
+
+
+#### Functions
+
+
+
+##### [function](eb42) `make-entity` payload data
+
+
+
+### CL-TELEGRAM-BOT/INLINE-KEYBOARD
+
+
+
+#### [package](b6c5) `cl-telegram-bot/inline-keyboard`
+
+
#### Classes
-
+
-##### CHANNEL
+##### CALLBACK-BUTTON
-
+
-###### [class](fafc) `channel` (base-group)
+###### [class](bd0d) `callback-button` (inline-keyboard-button)
-
+**Readers**
-##### CHAT
+
-
+###### [reader](94f5) `callback-button-data` (callback-button) (:data)
+
+
+
+##### INLINE-KEYBOARD-BUTTON
+
+
+
+###### [class](a93f) `inline-keyboard-button` ()
+
+Base class for all inline keyboard buttons.
-###### [class](6449) `chat` ()
+`API`: https://core.telegram.org/bots/api#inlinekeyboardbutton
**Readers**
-
+
-###### [reader](8739) `get-chat-id` (chat) (:id)
+###### [reader](8976) `button-text` (inline-keyboard-button) (:text)
-
+
-###### [reader](5b56) `get-has-protected-content` (chat) (:has-protected-content)
+##### INLINE-KEYBOARD
-
+
-###### [reader](f360) `get-message-auto-delete-time` (chat) (:message-auto-delete-time)
+###### [class](8fa6) `inline-keyboard` ()
-
+Represents an inline keyboard as specified in `API` https://core.telegram.org/bots/api#inlinekeyboardmarkup.
-###### [reader](44cb) `get-raw-data` (chat) (:raw-data)
+**Readers**
-
+
-###### [reader](b5a9) `get-username` (chat) (:username)
+###### [reader](271a) `keyboard-rows` (inline-keyboard) (:rows = nil)
-
+
-##### GROUP
+##### URL-BUTTON
-
+
-###### [class](e715) `group` (base-group)
+###### [class](60d8) `url-button` (inline-keyboard-button)
-
+**Readers**
-##### PRIVATE-CHAT
+
-
+###### [reader](7005) `button-url` (url-button) (:data)
+
+
+
+#### Functions
+
+
+
+##### [function](51ba) `answer-callback-query` bot callback &key text show-alert url
+
+https://core.telegram.org/bots/api#answercallbackquery
+
+
+
+##### [function](afe5) `callback-button` text data
+
+Creates a button which will call a callback.
+
+
+
+##### [function](f7cf) `inline-keyboard` rows
+
+Returns an inline keyboard which can be passed
+to `cl-telegram-bot/response:reply` ([`1`][0d9a] [`2`][9ce6]) as `REPLY-MARKUP` argument.
+
+Each row should be a list of [`inline-keyboard-button`][cc87] objects or a single
+object of this class. In latter case, such row will have only one button.
+
+
+
+##### [function](40cc) `url-button` text url
+
+Creates a button which will open an url.
+
+
+
+### CL-TELEGRAM-BOT/MARKUP
+
+
+
+#### [package](3abd) `cl-telegram-bot/markup`
+
+
+
+#### Generics
+
+
+
+##### [generic-function](ad51) `to-markup` obj
-###### [class](08e5) `private-chat` (chat)
+Transforms object into markup of Telegram `API`.
+
+Methods of this class should return a hash-table, representing `OBJ`
+in terms of Telegram `API`.
+
+
+
+### CL-TELEGRAM-BOT/MESSAGE
+
+
+
+#### [package](1672) `cl-telegram-bot/message`
+
+
+
+#### Classes
+
+
+
+##### ANIMATION-MESSAGE
+
+
+
+###### [class](59fb) `animation-message` (file-message)
+
+
+
+##### ANIMATION
+
+
+
+###### [class](1826) `animation` (file temporal spatial)
+
+
+
+##### AUDIO-MESSAGE
+
+
+
+###### [class](f2e3) `audio-message` (file-message)
+
+
+
+##### AUDIO
+
+
+
+###### [class](de9f) `audio` (file temporal)
**Readers**
-
+
-###### [reader](b448) `get-bio` (private-chat) (:bio)
+###### [reader](aca5) `get-performer` (audio) (:performer)
-
+Performer of the audio as defined by sender or by audio tags.
-###### [reader](895f) `get-first-name` (private-chat) (:first-name)
+
-
+###### [reader](3e1f) `get-title` (audio) (:title)
+
+Title of the audio as defined by sender or by audio tags.
-###### [reader](417f) `get-has-private-forwards` (private-chat) (:has-private-forwards)
+
-
+##### DOCUMENT-MESSAGE
-###### [reader](618b) `get-last-name` (private-chat) (:last-name)
+
-
+###### [class](0523) `document-message` (file-message)
-##### SUPER-GROUP
+
-
+##### DOCUMENT
+
+
+
+###### [class](b82f) `document` (file)
+
+
+
+##### FILE-MESSAGE
+
+
-###### [class](bb0c) `super-group` (base-group)
+###### [class](f075) `file-message` (message)
**Readers**
-
+
-###### [reader](0860) `get-can-set-sticker-set` (super-group) (:can-set-sticker-set)
+###### [reader](2ec9) `get-file` (file-message) (:file)
-
+
-###### [reader](2012) `get-join-by-request` (super-group) (:join-by-request)
+##### FILE
-
+
-###### [reader](c12e) `get-join-to-send-messages` (super-group) (:join-to-send-messages)
+###### [class](2fb8) `file` ()
-
+**Readers**
-###### [reader](1578) `get-slow-mode-delay` (super-group) (:slow-mode-delay)
+
-
+###### [reader](e88b) `get-file-id` (file) (:file-id)
-###### [reader](cabb) `get-sticker-set-name` (super-group) (:sticker-set-name)
+Identifier for this file, which can be used to download or reuse the file.
-
+
-#### Functions
+###### [reader](6399) `get-file-name` (file) (:file-name)
-
+Original filename as defined by sender.
-##### [function](88b9) `delete-chat-photo` bot-var1 chat
+
-https://core.telegram.org/bots/api#deletechatphoto
+###### [reader](7887) `get-file-size` (file) (:file-size)
-
+File size in bytes.
+
+
+
+###### [reader](fabb) `get-file-unique-id` (file) (:file-unique-id)
+
+Unique identifier for this file, which is supposed to be the same
+over time and for different bots. Can't be used to download or reuse
+the file.
+
+
+
+###### [reader](c579) `get-mime-type` (file) (:mime-type)
+
+`MIME` type of the file as defined by sender.
+
+
+
+##### MESSAGE
+
+
+
+###### [class](a17b) `message` ()
+
+**Readers**
+
+
+
+###### [reader](8cc1) `get-caption` (message) (:caption)
+
+Caption for the animation, audio, document, photo, video or voice.
+
+
+
+###### [reader](3727) `get-chat` (message) (:chat)
+
+
+
+###### [reader](e8a7) `get-entities` (message) (:entities = nil)
+
+
+
+###### [reader](bf5b) `get-forward-from` (message) (:forward-from)
+
+For forwarded messages, sender of the original message.
+
+
+
+###### [reader](d656) `get-forward-from-chat` (message) (:forward-from-chat)
+
+For messages forwarded from channels or from anonymous
+administrators, information about the original sender chat.
+
+
+
+###### [reader](2e8f) `get-forward-sender-name` (message) (:forward-sender-name)
+
+For forwarded messages, sender of the original message.
+
+
+
+###### [reader](2723) `get-message-id` (message) (:id)
+
+
+
+###### [reader](4b71) `get-raw-data` (message) (:raw-data)
+
+
+
+###### [reader](a685) `get-text` (message) (:text)
+
+
+
+##### PHOTO-MESSAGE
+
+
+
+###### [class](c8c2) `photo-message` (file-message)
+
+**Readers**
+
+
+
+###### [reader](07ba) `get-photo-options` (photo-message) (:photo-options)
+
+
+
+##### PHOTO
+
+
+
+###### [class](61f7) `photo` (file spatial)
+
+
+
+##### REPLY
+
+
+
+###### [class](f87a) `reply` (message)
+
+**Readers**
+
+
+
+###### [reader](9ba8) `get-reply-to-message` (reply) (:reply-to-message)
+
+
+
+##### SPATIAL
+
+
+
+###### [class](0963) `spatial` ()
+
+**Readers**
+
+
+
+###### [reader](e9fb) `get-height` (spatial) (:height)
+
+File height as defined by sender.
+
+
+
+###### [reader](7158) `get-width` (spatial) (:width)
+
+File width as defined by sender.
+
+
+
+##### STICKER-MESSAGE
+
+
+
+###### [class](9fc8) `sticker-message` (file-message)
+
+
+
+##### STICKER
+
+
+
+###### [class](839e) `sticker` (file spatial)
+
+**Readers**
+
+
+
+###### [reader](b37a) `get-emoji` (sticker) (:emoji)
+
+Emoji associated with the sticker
+
+
+
+###### [reader](408e) `get-is-animated` (sticker) (:is-animated)
+
+True if the sticker is animated.
+
+
+
+###### [reader](1270) `get-is-video` (sticker) (:is-video)
+
+True if the sticker is a video sticker.
+
+
+
+###### [reader](4d58) `get-set-name` (sticker) (:set-name)
+
+Name of the sticker set to which the sticker belongs.
+
+
+
+##### TEMPORAL
+
+
+
+###### [class](d3a6) `temporal` ()
+
+**Readers**
+
+
+
+###### [reader](d238) `get-duration` (temporal) (:duration)
+
+Duration of the file in seconds as defined by sender.
+
+
+
+##### UNISPATIAL
+
+
+
+###### [class](6e32) `unispatial` ()
+
+**Readers**
-##### [function](49f0) `export-chat-invite-link` bot-var1 chat
+
-https://core.telegram.org/bots/api#exportchatinvitelink
+###### [reader](48f1) `get-length` (unispatial) (:length)
-
+
-##### [function](8231) `get-chat-administrators` bot-var1 chat
+##### VIDEO-MESSAGE
-https://core.telegram.org/bots/api#getchatadministrators
+
-
+###### [class](4102) `video-message` (file-message)
-##### [function](5509) `get-chat-by-id` bot-var1 chat-id
+
-https://core.telegram.org/bots/api#getchat
+##### VIDEO-NOTE-MESSAGE
-
+
-##### [function](8324) `get-chat-member` bot-var1 chat user-id
+###### [class](161f) `video-note-message` (file-message)
-https://core.telegram.org/bots/api#getchatmember
+
-
+##### VIDEO-NOTE
-##### [function](3292) `get-chat-members-count` bot-var1 chat
+
-https://core.telegram.org/bots/api#getchatmemberscount
+###### [class](b17d) `video-note` (file temporal unispatial)
-
+
-##### [function](8411) `kick-chat-member` bot-var1 chat user-id until-date
+##### VIDEO
-https://core.telegram.org/bots/api#kickchatmember
+
-
+###### [class](8015) `video` (file temporal spatial)
-##### [function](b62a) `leave-chat` bot-var1 chat
+
-https://core.telegram.org/bots/api#leavechat
+##### VOICE-MESSAGE
-
+
-##### [function](4781) `pin-chat-message` bot-var1 chat message-id disable-notification
+###### [class](a64e) `voice-message` (file-message)
-https://core.telegram.org/bots/api#pinchatmessage
+
-
+##### VOICE
-##### [function](8137) `promote-chat-member` bot-var1 chat user-id can-change-info can-post-messages can-edit-messages can-delete-messages can-invite-users can-restrict-members can-pin-messages can-promote-members
+
-https://core.telegram.org/bots/api#promotechatmember
+###### [class](ff19) `voice` (file temporal)
-
+
-##### [function](02da) `restrict-chat-member` bot-var1 chat user-id until-date can-send-messages can-send-media-messages can-send-other-messages can-add-web-page-previews
+#### Generics
-https://core.telegram.org/bots/api#restrictchatmember
+
-
+##### [generic-function](0a3f) `on-message` bot text
-##### [function](d6df) `send-chat-action` bot-var1 chat action
+This method gets called with raw text from the message.
+By default it does nothing.
-https://core.telegram.org/bots/api#sendchataction
+
-
+##### [generic-function](e75a) `send-animation` bot chat animation &rest options &key caption parse-mode caption-entities duration width height thumb disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
-##### [function](cb11) `set-chat-description` bot-var1 chat description
+Sends animation to a chat.
-https://core.telegram.org/bots/api#setchatdescription
+
-
+##### [generic-function](90ee) `send-audio` bot chat audio &rest options &key caption parse-mode caption-entities duration performer title thumb disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
-##### [function](4df9) `set-chat-photo` bot-var1 chat photo
+
-https://core.telegram.org/bots/api#setchatphoto
+##### [generic-function](8eca) `send-document` bot chat document &rest options &key caption parse-mode caption-entities disable-content-type-detection thumb disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
-
+
-##### [function](7250) `set-chat-title` bot-var1 chat title
+##### [generic-function](b055) `send-photo` bot chat photo &rest options &key caption parse-mode caption-entities disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
-https://core.telegram.org/bots/api#setchattitle
+
-
+##### [generic-function](cf3b) `send-sticker` bot chat sticker &rest options &key disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
-##### [function](754a) `unban-chat-member` bot-var1 chat user-id
+A function to send sticker.
-https://core.telegram.org/bots/api#unbanchatmember
+
-
+##### [generic-function](105b) `send-video` bot chat video &rest options &key caption parse-mode caption-entities duration width height thumb disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
-##### [function](eb88) `unpin-chat-message` bot-var1 chat
+
-https://core.telegram.org/bots/api#unpinchatmessage
+##### [generic-function](e434) `send-video-note` bot chat video-note &rest options &key caption parse-mode caption-entities duration length thumb disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
-
+
-### CL-TELEGRAM-BOT/BOT
+##### [generic-function](a99e) `send-voice` bot chat voice &rest options &key caption parse-mode caption-entities duration disable-notification protect-content reply-to-message-id allow-sending-without-reply reply-markup
-
+
-#### [package](59cd) `cl-telegram-bot/bot`
+#### Functions
-
+
-#### Classes
+##### [function](a09d) `delete-message` bot chat message
-
+https://core.telegram.org/bots/api#deletemessage
-##### BOT
+
-
+##### [function](6b80) `forward-message` bot chat from-chat message &key disable-notification
-###### [class](3557) `bot` ()
+https://core.telegram.org/bots/api#forwardmessage
-**Readers**
+
-
+##### [function](cbc3) `get-current-chat`
-###### [reader](bd2c) `api-uri` (bot) (:API-URI = "https://api.telegram.org/")
+Returns a chat where currently processing message was received.
-
+
-###### [reader](949c) `file-endpoint` (bot) (:file-endpoint = nil)
+##### [function](3db2) `get-current-message`
-`HTTPS` file-endpoint
+Returns currently processed message.
-
+
-###### [reader](1b12) `get-endpoint` (bot) (:endpoint)
+##### [function](970b) `make-message` data
-`HTTPS` endpoint
+
-
+##### [function](9fc0) `send-message` bot chat text &rest options &key parse-mode disable-web-page-preview disable-notification reply-to-message-id reply-markup
-###### [reader](9808) `get-last-update-id` (bot) (= 0)
+https://core.telegram.org/bots/api#sendmessage
-Update id
+
-
+### CL-TELEGRAM-BOT/NETWORK
-###### [reader](73b2) `token` (bot) (:token = nil)
+
-Bot token given by BotFather
+#### [package](462b) `cl-telegram-bot/network`
-**Accessors**
+
-
+#### Classes
-###### [accessor](bd2c) `api-uri` (bot) (:API-URI = "https://api.telegram.org/")
+
-
+##### REQUEST-ERROR
-###### [accessor](949c) `file-endpoint` (bot) (:file-endpoint = nil)
+
-`HTTPS` file-endpoint
+###### [condition](39b5) `request-error` (error)
-
+**Readers**
-###### [accessor](9808) `get-last-update-id` (bot) (= 0)
+
-Update id
+###### [reader](39b5) `what` (request-error) (:what)
-
+
-###### [accessor](73b2) `token` (bot) (:token = nil)
+#### Functions
-Bot token given by BotFather
+
-
+##### [function](06f9) `make-request` bot name &rest options &key (streamp nil) (timeout 3) &allow-other-keys
-#### Macros
+Perform `HTTP` request to 'name `API` method with 'options `JSON`-encoded object.
-
+
-##### [macro](d03d) `defbot` name
+##### [function](cef9) `set-proxy` proxy
@@ -870,7 +1251,7 @@ Bot token given by BotFather
-#### [package](6cca) `cl-telegram-bot/pipeline`
+#### [package](9aa1) `cl-telegram-bot/pipeline`
@@ -878,7 +1259,7 @@ Bot token given by BotFather
-##### [generic-function](5337) `process` bot object
+##### [generic-function](0758) `process` bot object
This method is called by when processing a single update.
It is called multiple times on different parts of an update.
@@ -890,215 +1271,205 @@ For each update we call:
For each entity in payload:
process(entity)
-
+
-### CL-TELEGRAM-BOT/UPDATE
+### CL-TELEGRAM-BOT/RESPONSE
-
+
-#### [package](07ff) `cl-telegram-bot/update`
+#### [package](f54b) `cl-telegram-bot/response`
-
+
#### Classes
-
+
-##### UPDATE
+##### ALERT
-
+
-###### [class](341d) `update` ()
+###### [class](9c0b) `alert` (response-with-text)
-**Readers**
+
-
+##### NOTIFY
-###### [reader](e0e1) `get-payload` (update) (:payload)
+
-
+###### [class](1280) `notify` (response-with-text)
-###### [reader](de2f) `get-raw-data` (update) (:raw-data)
+
-
+##### OPEN-URL
-###### [reader](162d) `get-update-id` (update) (:id)
+
-
+###### [class](1824) `open-url` (response)
-#### Generics
+**Readers**
-
+
-##### [generic-function](5942) `process-updates` bot
+###### [reader](851f) `url-to-open` (open-url) (:text)
-By default, this method starts an infinite loop and fetching new updates using long polling.
+
-
+##### REPLY
-#### Functions
+
-
+###### [class](93c4) `reply` (response-with-text)
-##### [function](0526) `make-update` data
+
-
+##### RESPONSE-WITH-TEXT
-### CL-TELEGRAM-BOT/NETWORK
+
-
+###### [class](56b3) `response-with-text` (response)
-#### [package](59b3) `cl-telegram-bot/network`
+**Readers**
-
+
-#### Classes
+###### [reader](df64) `response-text` (response-with-text) (:text)
-
+
-##### REQUEST-ERROR
+##### RESPONSE
-
+
-###### [condition](35c2) `request-error` (error)
+###### [class](954c) `response` ()
**Readers**
-
+
-###### [reader](35c2) `what` (request-error) (:what)
+###### [reader](8d21) `rest-args` (response) (:args)
-
+
#### Functions
-
+
-##### [function](77b3) `make-request` bot name &rest options &key (streamp nil) (timeout 3) &allow-other-keys
+##### [function](eab2) `alert` text
-Perform `HTTP` request to 'name `API` method with 'options `JSON`-encoded object.
+Works like a [`send-message`][38a1], but only when an incoming message is processed.
+Automatically sends reply to a chat from where current message came from.
-
+
-##### [function](4834) `set-proxy` proxy
+##### [function](092c) `notify` text
-
+Works like a [`send-message`][38a1], but only when an incoming message is processed.
+Automatically sends reply to a chat from where current message came from.
-### CL-TELEGRAM-BOT/CORE
+
-
+##### [function](6f86) `open-url` url
-#### [package](c691) `cl-telegram-bot/core`
+Works like a [`send-message`][38a1], but only when an incoming message is processed.
+Automatically sends reply to a chat from where current message came from.
-
+
-#### Classes
+##### [function](0029) `reply` text &rest args &key parse-mode disable-web-page-preview disable-notification reply-to-message-id reply-markup (immediately t)
-
+Works like a [`send-message`][38a1], but only when an incoming message is processed.
+Automatically sends reply to a chat from where current message came from.
-##### REPLY
+
-
+### CL-TELEGRAM-BOT/RESPONSE-PROCESSING
-###### [class](2a1a) `reply` (message)
+
-**Readers**
+#### [package](9e38) `cl-telegram-bot/response-processing`
-
+
-###### [reader](2cce) `cl-telegram-bot/message:get-reply-to-message` (reply) (:reply-to-message)
+#### Classes
-
+
-#### Generics
+##### INTERRUPT-PROCESSING
-
+
-##### [generic-function](4cbd) `on-command` bot command rest-text
+###### [condition](5a91) `interrupt-processing` ()
-This method will be called for each command.
-First argument is a keyword. If user input was /save_note, then
-first argument will be :save-note.
+
-By default, logs call and does nothing.
+#### Generics
-
+
-##### [generic-function](9364) `on-message` bot text
+##### [generic-function](ba8d) `process-response` bot message response
-This method gets called with raw text from the message.
-By default it does nothing.
+Processes immediate responses of different types.
-
+
#### Functions
-
-
-##### [function](9c91) `reply` text &rest args &key parse-mode disable-web-page-preview disable-notification reply-to-message-id reply-markup
-
-Works like a send-message, but only when an incoming message is processed.
-Automatically sends reply to a chat from where current message came from.
-
-
-
-##### [function](4d7b) `start-processing` bot &key debug (delay-between-retries 10)
+
-
+##### [function](b5e7) `interrupt-processing`
-##### [function](9ed6) `stop-processing` bot
+
-
+### CL-TELEGRAM-BOT/UPDATE
-#### Macros
+
-
+#### [package](3f15) `cl-telegram-bot/update`
-##### [macro](d03d) `defbot` name
+
-
+#### Classes
-### CL-TELEGRAM-BOT/ENTITIES/COMMAND
+
-
+##### UPDATE
-#### [package](c9b2) `cl-telegram-bot/entities/command`
+
-
+###### [class](72ad) `update` ()
-#### Classes
+**Readers**
-
+
-##### BOT-COMMAND
+###### [reader](5113) `get-payload` (update) (:payload)
-
+
-###### [class](345a) `bot-command` (entity)
+###### [reader](70ac) `get-raw-data` (update) (:raw-data)
-**Readers**
+
-
+###### [reader](29b4) `get-update-id` (update) (:id)
-###### [reader](56b2) `get-command` (bot-command) (:command)
+
-
+#### Generics
-###### [reader](60b8) `get-rest-text` (bot-command) (:rest-text)
+
-
+##### [generic-function](c5b1) `process-updates` bot
-#### Generics
+By default, this method starts an infinite loop and fetching new updates using long polling.
-
+
-##### [generic-function](4cbd) `on-command` bot command rest-text
+#### Functions
-This method will be called for each command.
-First argument is a keyword. If user input was /save_note, then
-first argument will be :save-note.
+
-By default, logs call and does nothing.
+##### [function](e6ba) `make-update` data
@@ -1106,7 +1477,7 @@ By default, logs call and does nothing.
-#### [package](93b3) `cl-telegram-bot/utils`
+#### [package](961c) `cl-telegram-bot/utils`
@@ -1114,192 +1485,211 @@ By default, logs call and does nothing.
-##### [function](d4b4) `make-keyword` text
+##### [function](881e) `make-keyword` text
-##### [function](cbd1) `obfuscate` url
-
-
-
-### CL-TELEGRAM-BOT/ENTITIES/CORE
-
-
-
-#### [package](6ba8) `cl-telegram-bot/entities/core`
-
-
-
-#### Generics
-
-
-
-##### [generic-function](6f78) `make-entity-internal` entity-type payload data
-
-Extendable protocol to support entities of different kinds.
-First argument is a keyword, denoting a type of the entity.
-Payload is an object of type `message'.
-And data is a plist with data, describing the entity.
-
-
-
-#### Functions
-
-
-
-##### [function](02b5) `make-entity` payload data
+##### [function](aa03) `obfuscate` url
## Credits
* [Rei][b588] – initial version.
-
* [Alexander Artemenko][891d] – large refactoring, usage of `CLOS` classes, etc.
[6949]: https://40ants.com/cl-telegram-bot/
+[6611]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FCALLBACK-3ACALLBACK-20CLASS-29
+[1b93]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FCALLBACK-3AON-CALLBACK-20GENERIC-FUNCTION-29
+[56c0]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FENTITIES-2FCOMMAND-3AON-COMMAND-20GENERIC-FUNCTION-29
+[cc87]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FINLINE-KEYBOARD-3AINLINE-KEYBOARD-BUTTON-20CLASS-29
+[38a1]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FMESSAGE-3ASEND-MESSAGE-20FUNCTION-29
+[9ce6]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FRESPONSE-3AREPLY-20CLASS-29
+[0d9a]: https://40ants.com/cl-telegram-bot/#x-28CL-TELEGRAM-BOT-2FRESPONSE-3AREPLY-20FUNCTION-29
[53d1]: https://github.com/40ants/cl-telegram-bot
[7bb5]: https://github.com/40ants/cl-telegram-bot/actions
-[59cd]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/bot.lisp#L1
-[3557]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/bot.lisp#L17
-[9808]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/bot.lisp#L18
-[73b2]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/bot.lisp#L22
-[bd2c]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/bot.lisp#L27
-[1b12]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/bot.lisp#L31
-[949c]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/bot.lisp#L35
-[d03d]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/bot.lisp#L42
-[48f6]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L1
-[e715]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L111
-[bb0c]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L115
-[c12e]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L116
-[2012]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L118
-[1578]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L120
-[cabb]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L122
-[0860]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L124
-[fafc]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L127
-[5509]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L157
-[8411]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L163
-[754a]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L167
-[02da]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L171
-[8137]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L181
-[49f0]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L194
-[4df9]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L198
-[88b9]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L202
-[7250]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L206
-[cb11]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L210
-[4781]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L214
-[eb88]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L218
-[b62a]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L222
-[8231]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L226
-[3292]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L230
-[8324]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L234
-[d6df]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L238
-[6449]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L54
-[8739]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L55
-[b5a9]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L57
-[5b56]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L59
-[f360]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L61
-[44cb]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L63
-[08e5]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L83
-[895f]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L84
-[618b]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L86
-[b448]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L88
-[417f]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/chat.lisp#L90
-[c691]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/core.lisp#L1
-[4d7b]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/core.lisp#L34
-[9ed6]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/core.lisp#L57
-[c9b2]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/entities/command.lisp#L1
-[345a]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/entities/command.lisp#L22
-[56b2]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/entities/command.lisp#L23
-[60b8]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/entities/command.lisp#L26
-[4cbd]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/entities/command.lisp#L49
-[6ba8]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/entities/core.lisp#L1
-[6f78]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/entities/core.lisp#L23
-[02b5]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/entities/core.lisp#L36
-[287f]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L1
-[7b24]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L101
-[f214]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L105
-[38bc]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L108
-[15b9]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L112
-[f765]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L114
-[d8cb]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L118
-[5ea8]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L122
-[1326]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L144
-[ecd6]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L145
-[e6a1]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L151
-[1437]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L152
-[db4a]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L157
-[5561]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L163
-[9dce]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L164
-[1539]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L169
-[86bb]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L170
-[00ba]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L175
-[1559]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L182
-[2b03]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L187
-[29cf]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L192
-[d514]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L198
-[9d6d]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L200
-[e514]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L201
-[5257]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L206
-[7aad]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L212
-[1141]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L214
-[1593]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L216
-[6c67]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L218
-[99ec]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L220
-[cfb8]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L223
-[5da1]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L224
-[f513]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L228
-[d1ce]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L232
-[6f47]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L236
-[799d]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L253
-[b2c7]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L254
-[19f6]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L266
-[0418]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L268
-[eef7]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L270
-[4f9d]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L272
-[414a]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L273
-[6759]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L283
-[4c67]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L285
-[5e1e]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L287
-[6533]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L289
-[2a1a]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L291
-[2cce]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L292
-[5191]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L300
-[aec0]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L329
-[6344]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L345
-[f0a0]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L405
-[ed33]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L448
-[af9c]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L491
-[523c]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L534
-[6106]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L557
-[261c]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L600
-[524d]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L643
-[7691]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L701
-[6059]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L745
-[9c91]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L759
-[9364]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L784
-[3c79]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L824
-[5c0b]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L94
-[42cb]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L95
-[7c01]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/message.lisp#L97
-[59b3]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/network.lisp#L1
-[4834]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/network.lisp#L17
-[35c2]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/network.lisp#L20
-[77b3]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/network.lisp#L27
-[6cca]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/pipeline.lisp#L1
-[5337]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/pipeline.lisp#L8
-[32dc]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/telegram-call.lisp#L1
-[07ff]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/update.lisp#L1
-[341d]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/update.lisp#L22
-[162d]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/update.lisp#L23
-[e0e1]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/update.lisp#L25
-[de2f]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/update.lisp#L27
-[0526]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/update.lisp#L31
-[5942]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/update.lisp#L73
-[93b3]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/utils.lisp#L1
-[d4b4]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/utils.lisp#L17
-[cbd1]: https://github.com/40ants/cl-telegram-bot/blob/f7c9e6b91d5639a678f04173c8ac9cdd40ec1d8e/src/utils.lisp#L24
+[c186]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/bot.lisp#L1
+[0c3b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/bot.lisp#L19
+[e7ba]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/bot.lisp#L20
+[419d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/bot.lisp#L24
+[3953]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/bot.lisp#L29
+[4476]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/bot.lisp#L33
+[37ea]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/bot.lisp#L37
+[86e0]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/bot.lisp#L42
+[30e6]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/bot.lisp#L47
+[cce0]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/bot.lisp#L55
+[96f5]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/callback.lisp#L1
+[ea25]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/callback.lisp#L27
+[7a40]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/callback.lisp#L28
+[322a]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/callback.lisp#L31
+[ada7]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/callback.lisp#L34
+[7289]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/callback.lisp#L39
+[a9c2]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/callback.lisp#L46
+[e9df]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/callback.lisp#L76
+[3a02]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L1
+[e783]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L111
+[e8be]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L115
+[8d78]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L116
+[79c0]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L118
+[ddc3]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L120
+[315b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L122
+[2807]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L124
+[0a0d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L127
+[0b6d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L157
+[f22b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L163
+[8eec]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L167
+[139f]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L171
+[facd]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L181
+[aa79]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L194
+[80ea]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L198
+[e8ed]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L202
+[32e3]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L206
+[1403]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L210
+[0e8e]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L214
+[0e36]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L218
+[7d1f]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L222
+[905e]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L226
+[bdd6]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L230
+[469a]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L234
+[11f4]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L238
+[149d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L54
+[ebda]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L55
+[6f83]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L57
+[b233]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L59
+[38fa]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L61
+[622d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L63
+[4fc7]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L83
+[6b6d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L84
+[8c65]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L86
+[b76c]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L88
+[1a8d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/chat.lisp#L90
+[6b7a]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/core.lisp#L1
+[2385]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/core.lisp#L37
+[e95e]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/core.lisp#L72
+[769c]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/entities/command.lisp#L1
+[3367]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/entities/command.lisp#L36
+[bd97]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/entities/command.lisp#L37
+[f5bd]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/entities/command.lisp#L40
+[620e]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/entities/command.lisp#L63
+[e8b5]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/entities/core.lisp#L1
+[6a05]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/entities/core.lisp#L23
+[eb42]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/entities/core.lisp#L36
+[b6c5]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L1
+[8fa6]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L23
+[271a]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L24
+[a93f]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L31
+[8976]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L32
+[bd0d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L40
+[94f5]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L41
+[60d8]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L46
+[7005]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L47
+[f7cf]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L52
+[afe5]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L62
+[40cc]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L67
+[51ba]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/inline-keyboard.lisp#L73
+[3abd]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/markup.lisp#L1
+[ad51]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/markup.lisp#L7
+[1672]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L1
+[a685]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L101
+[8cc1]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L105
+[3727]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L109
+[e8a7]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L112
+[4b71]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L116
+[bf5b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L118
+[2e8f]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L122
+[d656]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L126
+[d3a6]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L148
+[d238]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L149
+[0963]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L155
+[e9fb]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L156
+[7158]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L161
+[6e32]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L167
+[48f1]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L168
+[2fb8]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L173
+[e88b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L174
+[fabb]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L179
+[6399]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L186
+[7887]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L191
+[c579]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L196
+[61f7]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L202
+[de9f]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L204
+[aca5]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L205
+[3e1f]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L210
+[1826]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L216
+[b82f]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L218
+[8015]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L220
+[b17d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L222
+[ff19]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L224
+[839e]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L227
+[408e]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L228
+[1270]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L232
+[b37a]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L236
+[4d58]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L240
+[f075]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L257
+[2ec9]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L258
+[f2e3]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L270
+[0523]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L272
+[59fb]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L274
+[c8c2]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L276
+[07ba]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L277
+[9fc8]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L287
+[4102]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L289
+[161f]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L291
+[a64e]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L293
+[f87a]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L296
+[9ba8]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L297
+[970b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L307
+[9fc0]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L336
+[b055]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L355
+[90ee]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L415
+[8eca]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L458
+[105b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L501
+[e75a]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L544
+[e434]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L567
+[a99e]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L610
+[cf3b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L653
+[6b80]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L711
+[a09d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L755
+[0a3f]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L762
+[3db2]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L798
+[cbc3]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L805
+[a17b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L98
+[2723]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/message.lisp#L99
+[462b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/network.lisp#L1
+[cef9]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/network.lisp#L19
+[39b5]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/network.lisp#L22
+[06f9]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/network.lisp#L29
+[9aa1]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/pipeline.lisp#L1
+[0758]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/pipeline.lisp#L8
+[9e38]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response-processing.lisp#L1
+[ba8d]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response-processing.lisp#L12
+[b5e7]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response-processing.lisp#L16
+[5a91]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response-processing.lisp#L8
+[f54b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L1
+[eab2]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L110
+[6f86]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L124
+[954c]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L31
+[8d21]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L32
+[56b3]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L37
+[df64]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L38
+[93c4]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L42
+[1280]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L46
+[9c0b]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L50
+[1824]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L54
+[851f]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L55
+[0029]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L61
+[092c]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/response.lisp#L96
+[3f15]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/update.lisp#L1
+[72ad]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/update.lisp#L25
+[29b4]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/update.lisp#L26
+[5113]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/update.lisp#L28
+[70ac]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/update.lisp#L30
+[e6ba]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/update.lisp#L38
+[c5b1]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/update.lisp#L97
+[961c]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/utils.lisp#L1
+[881e]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/utils.lisp#L17
+[aa03]: https://github.com/40ants/cl-telegram-bot/blob/a803015110d172fa18138abecf2bb3ad2e15d7e3/src/utils.lisp#L24
[5798]: https://github.com/40ants/cl-telegram-bot/issues
[b588]: https://github.com/sovietspaceship
[891d]: https://github.com/svetlyak40wt
@@ -1314,6 +1704,7 @@ And data is a plist with data, describing the entity.
[5186]: https://quickdocs.org/kebab
[7f8b]: https://quickdocs.org/log4cl
[c41d]: https://quickdocs.org/serapeum
+[ef7f]: https://quickdocs.org/str
[fc0e]: https://quickdocs.org/trivial-backtrace
* * *
diff --git a/cl-telegram-bot.asd b/cl-telegram-bot.asd
index cdfeacf..562441e 100644
--- a/cl-telegram-bot.asd
+++ b/cl-telegram-bot.asd
@@ -11,3 +11,6 @@
:pathname "src"
:depends-on ("cl-telegram-bot/core")
:in-order-to ((test-op (test-op "cl-telegram-bot-tests"))))
+
+
+(asdf:register-system-packages "log4cl" '("LOG"))
diff --git a/docs/changelog.lisp b/docs/changelog.lisp
index d267e92..6f5b5b2 100644
--- a/docs/changelog.lisp
+++ b/docs/changelog.lisp
@@ -11,6 +11,30 @@
"REPL"
"CL-TELEGRAM-BOT/MESSAGE:REPLY"
"HTTP"))
+ (0.6.0 2024-10-15
+ "
+Changed
+=======
+
+* CL-TELEGRAM-BOT/CHAT:GET-CHAT generic-function is now exported from cl-telegram-bot/chat package instead of cl-telegram-bot/message.
+ Also, now it is applicable to updates, and other objects which can be associated with a chat.
+* `callback-chat` function was removed from cl-telegram-bot/callback package. Use abovementionned `get-chat` generic-function.
+
+Added
+=====
+
+* CL-TELEGRAM-BOT/BOT:BOT-INFO was added to CL-TELEGRAM-BOT/BOT:BOT class.
+* Macro CL-TELEGRAM-BOT/BOT:DEFBOT now accepts optional slots and options like DEFCLASS macro does.
+* Class CL-TELEGRAM-BOT/ENTITIES/COMMAND:BOT-COMMAND now has bot-username slot and CL-TELEGRAM-BOT/ENTITIES/COMMAND:ON-COMMAND
+ generic-function is called in a group chat only if the command was addressed to a current bot. Previously, bot was not
+ able to process commands in group chats.
+* Some kinds of messages are wrapped into an envelope class now to distinguish between edited message, channel posts, and edited channel post. These envelope classes are gathered in package cl-telegram-bot/envelope.
+* CL-TELEGRAM-BOT/MESSAGE:GET-SENDER-CHAT was added.
+* CL-TELEGRAM-BOT/MESSAGE:GET-CURRENT-BOT function was added.
+* Functions CL-TELEGRAM-BOT/PAYMENTS:SEND-INVOICE, CL-TELEGRAM-BOT/PAYMENTS:ANSWER-SHIPPING-QUERY and CL-TELEGRAM-BOT/PAYMENTS:ANSWER-PRE-CHECKOUT-QUERY were fixed.
+* Package cl-telegram-bot/user was added with a bunch of classes and functions.
+
+")
(0.5.0 2024-02-18
"
Added
diff --git a/docs/index.lisp b/docs/index.lisp
index b5bd918..3180f90 100644
--- a/docs/index.lisp
+++ b/docs/index.lisp
@@ -43,6 +43,7 @@
"HTTP"
"HTTPS"
"MIME"
+ "CL"
"TODO"
"MIT"
"API"
diff --git a/src/bot.lisp b/src/bot.lisp
index 10da619..8a0b4a4 100644
--- a/src/bot.lisp
+++ b/src/bot.lisp
@@ -11,9 +11,9 @@
#:get-endpoint
#:get-last-update-id
#:token
- #:sent-commands-cache))
-
-(in-package cl-telegram-bot/bot)
+ #:sent-commands-cache
+ #:bot-info))
+(in-package #:cl-telegram-bot/bot)
(defclass bot ()
@@ -39,6 +39,9 @@
:accessor file-endpoint
:documentation "HTTPS file-endpoint"
:initform nil)
+ (bot-info :initform nil
+ :documentation "This slot will be filled with CL-TELEGRAM-BOT/USER:USER object on first access using a call to CL-TELEGRAM-BOT/USER:GET-ME function."
+ :reader bot-info)
(debug-mode
:initform nil
:initarg :debug-mode
@@ -52,10 +55,12 @@
:accessor sent-commands-cache)))
-(defmacro defbot (name)
+(defmacro defbot (name &optional slots options)
+ "Use this macro to define a class of your Telegram bot."
`(progn
(defclass ,name (bot)
- ())
+ ,slots
+ ,@options)
(defun ,(alexandria:symbolicate 'make- name) (token &rest args)
(apply 'make-instance
@@ -79,5 +84,3 @@
(bot stream :type t)
(format stream
"id=~A" (get-last-update-id bot))))
-
-
diff --git a/src/callback.lisp b/src/callback.lisp
index 0dfcba5..ef3cf23 100644
--- a/src/callback.lisp
+++ b/src/callback.lisp
@@ -2,7 +2,6 @@
(:use #:cl)
(:import-from #:cl-telegram-bot/message
#:message
- #:get-chat
#:*current-message*
#:get-rest-args
#:get-text
@@ -11,6 +10,7 @@
(:import-from #:cl-telegram-bot/pipeline
#:process)
(:import-from #:cl-telegram-bot/chat
+ #:get-chat
#:get-chat-id)
(:import-from #:cl-telegram-bot/response-processing
#:process-response)
@@ -19,8 +19,7 @@
#:make-callback
#:on-callback
#:callback-id
- #:callback-message
- #:callback-chat))
+ #:callback-message))
(in-package #:cl-telegram-bot/callback)
@@ -73,8 +72,5 @@
(values))
-(defgeneric callback-chat (callback)
- (:documentation "Returns a chat from where callback was sent.")
-
- (:method ((callback callback))
- (cl-telegram-bot/message:get-chat (callback-message callback))))
+(defmethod get-chat ((callback callback))
+ (get-chat (callback-message callback)))
diff --git a/src/chat.lisp b/src/chat.lisp
index 860ea39..6e65779 100644
--- a/src/chat.lisp
+++ b/src/chat.lisp
@@ -47,7 +47,8 @@
#:get-slow-mode-delay
#:get-join-by-request
#:get-join-to-send-messages
- #:get-can-set-sticker-set))
+ #:get-can-set-sticker-set
+ #:get-chat))
(in-package cl-telegram-bot/chat)
@@ -237,3 +238,13 @@
(def-telegram-call send-chat-action (chat action)
"https://core.telegram.org/bots/api#sendchataction")
+
+
+
+(defgeneric get-chat (obj)
+ (:documentation "Returns a chat associated with object.
+
+ Object could be a message, update, callback, etc. Should return an object of CHAT class or NIL.
+ Some types of updates aren't bound to a chat. In this case a method should return NIL.")
+ (:method ((obj t))
+ (values nil)))
diff --git a/src/entities/command.lisp b/src/entities/command.lisp
index c0a3157..229455e 100644
--- a/src/entities/command.lisp
+++ b/src/entities/command.lisp
@@ -1,9 +1,10 @@
(uiop:define-package #:cl-telegram-bot/entities/command
(:use #:cl)
(:import-from #:log4cl)
- (:import-from #:cl-telegram-bot/entities/core
- #:entity
+ (:import-from #:cl-telegram-bot/entities/generic
#:make-entity-internal)
+ (:import-from #:cl-telegram-bot/entities/core
+ #:entity)
(:import-from #:cl-telegram-bot/message
#:message
#:get-text)
@@ -12,6 +13,7 @@
(:import-from #:cl-telegram-bot/pipeline
#:process)
(:import-from #:cl-telegram-bot/bot
+ #:bot-info
#:bot
#:sent-commands-cache)
(:import-from #:alexandria
@@ -26,8 +28,11 @@
#:set-my-commands)
(:import-from #:str
#:replace-all)
+ (:import-from #:cl-telegram-bot/user
+ #:username)
(:export #:get-command
#:bot-command
+ #:bot-username
#:get-rest-text
#:on-command))
(in-package #:cl-telegram-bot/entities/command)
@@ -37,6 +42,9 @@
((command :type keyword
:initarg :command
:reader get-command)
+ (bot-username :type (or null string)
+ :initarg :bot-username
+ :reader bot-username)
(rest-text :type string
:initarg :rest-text
:reader get-rest-text)))
@@ -44,20 +52,27 @@
(defmethod make-entity-internal ((entity-type (eql :bot-command))
(payload message) data)
- (declare (ignorable payload entity-type))
+ (declare (ignorable entity-type))
(let* ((text (get-text payload))
(offset (getf data :|offset|))
(length (getf data :|length|))
- (command (make-keyword (subseq text
- (+ offset 1)
- (+ offset length))))
+ (command-and-probably-bot-username
+ (subseq text
+ (+ offset 1)
+ (+ offset length)))
(rest-text (string-trim " "
(subseq text
(+ offset length)))))
- (make-instance 'bot-command
- :command command
- :rest-text rest-text
- :raw-data data)))
+ (destructuring-bind (command &optional bot-username)
+ (str:split #\@ command-and-probably-bot-username
+ :omit-nulls t
+ :limit 2)
+ (make-instance 'bot-command
+ :command (make-keyword command)
+ :payload payload
+ :bot-username bot-username
+ :rest-text rest-text
+ :raw-data data))))
(defgeneric on-command (bot command rest-text)
@@ -129,7 +144,11 @@
(setf (sent-commands-cache bot)
(update-commands bot
:command-name-to-check command-str-name)))
-
- (on-command bot
- command-name
- (get-rest-text command))))
+
+ (when (or (null (bot-username command))
+ (string-equal (bot-username command)
+ (username (bot-info bot))))
+ (on-command bot
+ command-name
+ (get-rest-text command)))))
+
diff --git a/src/entities/core.lisp b/src/entities/core.lisp
index e304678..2ee78fd 100644
--- a/src/entities/core.lisp
+++ b/src/entities/core.lisp
@@ -1,18 +1,24 @@
-(defpackage #:cl-telegram-bot/entities/core
+(uiop:define-package #:cl-telegram-bot/entities/core
(:use #:cl)
(:import-from #:cl-telegram-bot/utils
#:make-keyword)
(:import-from #:arrows
#:->)
- (:nicknames #:cl-telegram-bot/entities)
- (:export
- #:make-entity
- #:make-entity-internal))
+ (:import-from #:cl-telegram-bot/message
+ #:message)
+ (:import-from #:cl-telegram-bot/chat
+ #:get-chat)
+ (:import-from #:cl-telegram-bot/entities/generic
+ #:make-entity-internal)
+ (:nicknames #:cl-telegram-bot/entities))
(in-package cl-telegram-bot/entities/core)
(defclass entity ()
- ((raw-data :initarg :raw-data
+ ((payload :type message
+ :initarg :payload
+ :reader get-payload)
+ (raw-data :initarg :raw-data
:reader get-raw-data)))
@@ -20,23 +26,12 @@
())
-(defgeneric make-entity-internal (entity-type payload data)
- (:documentation "Extendable protocol to support entities of different kinds.
- First argument is a keyword, denoting a type of the entity.
- Payload is an object of type `message'.
- And data is a plist with data, describing the entity."))
-
-
(defmethod make-entity-internal (entity-type payload data)
(declare (ignorable payload entity-type))
(make-instance 'unsupported-entity
- :raw-data data))
+ :raw-data data
+ :payload payload))
-(defun make-entity (payload data)
- (let ((entity-type (-> data
- (getf :|type|)
- (make-keyword))))
- (make-entity-internal entity-type
- payload
- data)))
+(defmethod get-chat ((command entity))
+ (get-chat (get-payload command)))
diff --git a/src/entities/generic.lisp b/src/entities/generic.lisp
new file mode 100644
index 0000000..7ff212d
--- /dev/null
+++ b/src/entities/generic.lisp
@@ -0,0 +1,25 @@
+(uiop:define-package #:cl-telegram-bot/entities/generic
+ (:use #:cl)
+ (:import-from #:cl-telegram-bot/utils
+ #:make-keyword)
+ (:import-from #:arrows
+ #:->)
+ (:export #:make-entity
+ #:make-entity-internal))
+(in-package #:cl-telegram-bot/entities/generic)
+
+
+(defgeneric make-entity-internal (entity-type payload data)
+ (:documentation "Extendable protocol to support entities of different kinds.
+ First argument is a keyword, denoting a type of the entity.
+ Payload is an object of type `message'.
+ And data is a plist with data, describing the entity."))
+
+
+(defun make-entity (payload data)
+ (let ((entity-type (-> data
+ (getf :|type|)
+ (make-keyword))))
+ (make-entity-internal entity-type
+ payload
+ data)))
diff --git a/src/envelope.lisp b/src/envelope.lisp
new file mode 100644
index 0000000..fec8c42
--- /dev/null
+++ b/src/envelope.lisp
@@ -0,0 +1,65 @@
+(uiop:define-package #:cl-telegram-bot/envelope
+ (:use #:cl)
+ (:import-from #:cl-telegram-bot/pipeline
+ #:process)
+ (:export #:wrapped-message
+ #:envelope
+ #:edited-message
+ #:channel-post
+ #:edited-channel-post
+ #:edited-message-p
+ #:channel-post-p))
+(in-package #:cl-telegram-bot/envelope)
+
+
+(defvar *wrappers* nil
+ "This var will hold a list of wrappers during the call to PROCESS generic-function. It is used by functions CHANNEL-POST-P and EDITED-MESSAGE-P.")
+
+
+(defclass envelope ()
+ ((message :initarg :message
+ :reader wrapped-message))
+ (:documentation "This is the container for a message. From the type of container we can understand if this message was sent to a channel or maybe edited, etc."))
+
+
+(defclass edited-message (envelope)
+ ()
+ (:documentation "This container wraps CL-TELEGRAM-BOT/MESSAGE:MESSAGE when user edits a message."))
+
+
+(defclass channel-post (envelope)
+ ()
+ (:documentation "This container wraps CL-TELEGRAM-BOT/MESSAGE:MESSAGE when somebody sends a message to a channel."))
+
+
+(defclass edited-channel-post (envelope)
+ ()
+ (:documentation "This container wraps CL-TELEGRAM-BOT/MESSAGE:MESSAGE when somebody edits a message in a channel."))
+
+
+(defmethod process ((bot t) (envelope envelope))
+ "By default, just calls `process' on the wrapped message."
+ (log:debug "Processing envelope" envelope)
+ (let ((message (wrapped-message envelope))
+ (*wrappers* (cons envelope *wrappers*)))
+ (process bot message)))
+
+
+(declaim (ftype (function () boolean)
+ channel-post-p))
+
+(defun channel-post-p ()
+ "Returns T if current message was posted to a channel."
+ (loop for wrapper in *wrappers*
+ thereis (or (typep wrapper 'channel-post)
+ (typep wrapper 'edited-channel-post))))
+
+
+(declaim (ftype (function () boolean)
+ edited-message-p))
+
+(defun edited-message-p ()
+ "Returns T if current message is an update for existing message in the channel of group chat."
+ (loop for wrapper in *wrappers*
+ thereis (or (typep wrapper 'edited-message)
+ (typep wrapper 'edited-channel-post))))
diff --git a/src/inline-keyboard.lisp b/src/inline-keyboard.lisp
index fb462b1..91ab9ae 100644
--- a/src/inline-keyboard.lisp
+++ b/src/inline-keyboard.lisp
@@ -28,6 +28,11 @@
(:documentation "Represents an inline keyboard as specified in API https://core.telegram.org/bots/api#inlinekeyboardmarkup."))
+(defmethod print-object ((obj inline-keyboard) stream)
+ (print-unreadable-object (obj stream :type t)
+ (format stream "~S" (keyboard-rows obj))))
+
+
(defclass inline-keyboard-button ()
((text :initarg :text
:type string
@@ -37,6 +42,11 @@
API: https://core.telegram.org/bots/api#inlinekeyboardbutton"))
+(defmethod print-object ((obj inline-keyboard-button) stream)
+ (print-unreadable-object (obj stream :type t)
+ (format stream "~S" (button-text obj))))
+
+
(defclass callback-button (inline-keyboard-button)
((data :initarg :data
:type string
diff --git a/src/message.lisp b/src/message.lisp
index db9e930..b8f3b61 100644
--- a/src/message.lisp
+++ b/src/message.lisp
@@ -4,8 +4,9 @@
(:import-from #:cl-telegram-bot/chat
#:get-chat-id
#:make-chat
- #:chat)
- (:import-from #:cl-telegram-bot/entities/core
+ #:chat
+ #:get-chat)
+ (:import-from #:cl-telegram-bot/entities/generic
#:make-entity)
(:import-from #:cl-telegram-bot/network
#:make-request)
@@ -16,10 +17,13 @@
(:import-from #:serapeum
#:defvar-unbound)
(:import-from #:cl-telegram-bot/utils
+ #:split-by-lines
#:def-telegram-call)
(:import-from #:cl-telegram-bot/response-processing
#:process-response
#:interrupt-processing)
+ (:import-from #:alexandria
+ #:remove-from-plistf)
(:export #:animation
#:animation-message
#:audio
@@ -31,7 +35,6 @@
#:file-message
#:forward-message
#:get-caption
- #:get-chat
#:get-current-chat
#:get-current-message
#:get-duration
@@ -84,7 +87,9 @@
#:video-note
#:video-note-message
#:voice
- #:voice-message))
+ #:voice-message
+ #:get-sender-chat
+ #:get-current-bot))
(in-package cl-telegram-bot/message)
@@ -115,6 +120,10 @@
:reader get-entities)
(raw-data :initarg :raw-data
:reader get-raw-data)
+ (sender-chat :initarg :sender-chat
+ :type (or null chat)
+ :reader get-sender-chat
+ :documentation "Sender of the message, sent on behalf of a chat. For example, the channel itself for channel posts, the supergroup itself for messages from anonymous group administrators, the linked channel for messages automatically forwarded to the discussion group.")
(forward-from :initarg :forward-from
:type (or null chat)
:reader get-forward-from
@@ -138,6 +147,8 @@ administrators, information about the original sender chat.")))
(make-entity message item))
(getf data :|entities|))
(slot-value message 'raw-data) data
+ (slot-value message 'sender-chat) (when (getf data :|sender_chat|)
+ (make-chat (getf data :|sender_chat|)))
(slot-value message 'forward-from-chat) (when (getf data :|forward_from_chat|)
(make-chat (getf data :|forward_from_chat|)))
(slot-value message 'forward-from) (when (getf data :|forward_from|)
@@ -336,27 +347,40 @@ the file.")
(defun send-message (bot chat text
&rest options
&key parse-mode
- disable-web-page-preview
- disable-notification
- reply-to-message-id
- reply-markup)
+ disable-web-page-preview
+ disable-notification
+ reply-to-message-id
+ (autosplit nil)
+ reply-markup)
"https://core.telegram.org/bots/api#sendmessage"
(declare (ignorable parse-mode disable-web-page-preview disable-notification
reply-to-message-id reply-markup))
(log:debug "Sending message" chat text)
- (apply #'make-request bot "sendMessage"
- :|chat_id| (typecase chat
- (string chat)
- (t
- (get-chat-id chat)))
- :|text| text
- options))
+
+ (remove-from-plistf options :autosplit)
+
+ (flet ((send (text)
+ (apply #'make-request bot "sendMessage"
+ :|chat_id| (typecase chat
+ (string chat)
+ (t
+ (get-chat-id chat)))
+ :|text| text
+ options)))
+ (cond
+ ((and (serapeum:length< 4096 text)
+ autosplit)
+ (mapc #'send
+ (split-by-lines text :max-size 4096)))
+ (t
+ (send text)))))
+
(defgeneric send-photo (bot chat photo
- &rest options
- &key caption parse-mode caption-entities
- disable-notification protect-content reply-to-message-id
- allow-sending-without-reply reply-markup))
+ &rest options
+ &key caption parse-mode caption-entities
+ disable-notification protect-content reply-to-message-id
+ allow-sending-without-reply reply-markup))
(defmethod send-photo (bot chat (photo string)
&rest options
@@ -795,6 +819,13 @@ https://core.telegram.org/bots/api#sendsticker"
(values))
+(defun get-current-bot ()
+ "Returns a bot to which message was addressed."
+ (unless (boundp '*current-bot*)
+ (error "Seems (get-current-bot) was called outside of processing pipeline, because no current bot is available."))
+ (values *current-bot*))
+
+
(defun get-current-message ()
"Returns currently processed message."
(unless (boundp '*current-message*)
diff --git a/src/network.lisp b/src/network.lisp
index a9ca8dc..e133e14 100644
--- a/src/network.lisp
+++ b/src/network.lisp
@@ -2,7 +2,8 @@
(:use #:cl)
(:import-from #:alexandria)
(:import-from #:dexador)
- (:import-from #:log4cl)
+ (:import-from #:log)
+ (:import-from #:yason)
(:import-from #:cl-telegram-bot/utils
#:obfuscate)
(:import-from #:cl-telegram-bot/bot
@@ -12,7 +13,7 @@
#:request-error
#:set-proxy
#:what))
-(in-package cl-telegram-bot/network)
+(in-package #:cl-telegram-bot/network)
(defvar *proxy* nil)
@@ -41,19 +42,31 @@
collect (kebab:to-snake-case key)
and
collect value))
+ (encoded-content (jonathan:to-json processed-options))
(response
(if *proxy*
(dexador:post url
:headers '(("Content-Type" . "application/json"))
- :content (jonathan:to-json processed-options)
+ :content encoded-content
:read-timeout max-timeout
:connect-timeout max-timeout
:proxy *proxy*)
- (dexador:post url
- :headers '(("Content-Type" . "application/json"))
- :content (jonathan:to-json processed-options)
- :read-timeout max-timeout
- :connect-timeout max-timeout)))
+ (handler-bind ((dexador.error:http-request-too-many-requests
+ (lambda (err)
+ (let* ((response (dexador:response-body err))
+ (data (yason:parse response))
+ (sleep-time (or (serapeum:href data "parameters" "retry_after")
+ (progn
+ (log:warn "Unable to get parameters->retry_after from" response)
+ 10))))
+ (sleep sleep-time)
+ (dexador:retry-request err))))
+ (dexador.error:http-request-bad-gateway #'dexador:retry-request))
+ (dexador:post url
+ :headers '(("Content-Type" . "application/json"))
+ :content (jonathan:to-json processed-options)
+ :read-timeout max-timeout
+ :connect-timeout max-timeout))))
(data (jonathan:parse response)))
(unless (getf data :|ok|)
(log:error "Wrong data received from the server" data)
diff --git a/src/payments.lisp b/src/payments.lisp
index 71199b9..b2c4f3e 100644
--- a/src/payments.lisp
+++ b/src/payments.lisp
@@ -1,53 +1,218 @@
-(defpackage #:cl-telegram-bot/payments
- (:use #:cl))
+(uiop:define-package #:cl-telegram-bot/payments
+ (:use #:cl)
+ (:import-from #:log)
+ (:import-from #:cl-telegram-bot/network
+ #:make-request)
+ (:import-from #:cl-telegram-bot/user
+ #:get-user-info
+ #:make-user-from-raw
+ #:user)
+ (:import-from #:cl-telegram-bot/pipeline
+ #:process)
+ (:import-from #:cl-telegram-bot/message
+ #:*current-message*
+ #:*current-bot*)
+ (:import-from #:serapeum
+ #:->)
+ (:import-from #:cl-telegram-bot/bot
+ #:bot)
+ (:export
+ #:on-pre-checkout-query
+ #:answer-pre-checkout-query
+ #:send-invoice
+ #:answer-shipping-query))
(in-package cl-telegram-bot/payments)
-;; TODO: refactor
+(defclass pre-checkout-query ()
+ ((id :initarg :id
+ :type string
+ :reader pre-checkout-query-id)
+ (invoice-payload :initarg :invoice-payload
+ :type string
+ :reader invoice-payload)
+ (total-amount :initarg :total-amount
+ :type integer
+ :reader total-amount)
+ (currency :initarg :currency
+ :type string
+ :reader currency)
+ (raw-data :initarg :raw-data
+ :reader callback-raw-data)
+ (user :initarg :user
+ :type user
+ :reader pre-checkout-query-user)))
+
+
+(defclass successful-payment ()
+ ((provider-payment-charge-id :initarg :provider-payment-charge-id
+ :type string
+ :reader provider-payment-charge-id)
+ (telegram-payment-charge-id :initarg :telegram-payment-charge-id
+ :type string
+ :reader telegram-payment-charge-id)
+ (invoice-payload :initarg :invoice-payload
+ :type string
+ :reader invoice-payload)
+ (total-amount :initarg :total-amount
+ :type integer
+ :reader total-amount)
+ (currency :initarg :currency
+ :type string
+ :reader currency)
+ (shipping-option-id :initarg :shipping-option-id
+ :type (or null string)
+ :initform nil
+ :reader shipping-option-id)
+ (raw-data :initarg :raw-data
+ :reader get-raw-data)
+ ;; TODO: support optional OrderInfo
+ ))
+
(defun send-invoice (b chat-id title description payload provider-token start-parameter currency prices &key photo-url photo-size photo-width photo-height need-name need-phone-number need-email need-shipping-address is-flexible disable-notification reply-to-message-id reply-markup)
"https://core.telegram.org/bots/api#sendinvoice"
(let ((options
- (list
- (cons :chat_id chat-id)
- (cons :title title)
- (cons :description description)
- (cons :payload payload)
- (cons :provider_token provider-token)
- (cons :start_parameter start-parameter)
- (cons :currency currency)
- (cons :prices prices))))
- (when photo-url (nconc options `((:photo_url . ,photo-url))))
- (when photo-size (nconc options `((:photo_size . ,photo-size))))
- (when photo-width (nconc options `((:photo_width . ,photo-width))))
- (when photo-height (nconc options `((:photo_height . ,photo-height))))
- (when need-name (nconc options `((:need_name . ,need-name))))
- (when need-phone-number (nconc options `((:need_phone_number . ,need-phone-number))))
- (when need-email (nconc options `((:need_email . ,need-email))))
- (when need-shipping-address (nconc options `((:need_shipping_address . ,need-shipping-address))))
- (when is-flexible (nconc options `((:is_flexible . ,is-flexible))))
- (when disable-notification (nconc options `((:disable_notification . ,disable-notification))))
- (when reply-to-message-id (nconc options `((:reply_to_message_id . ,reply-to-message-id))))
- (when reply-markup (nconc options `((:reply_markup . ,reply-markup))))
+ (append
+ (list
+ :chat_id chat-id
+ :title title
+ :description description
+ :payload payload
+ :provider_token provider-token
+ :start_parameter start-parameter
+ :currency currency
+ :prices prices)
+ (when photo-url
+ (list :photo_url photo-url))
+ (when photo-size
+ (list :photo_size photo-size))
+ (when photo-width
+ (list :photo_width photo-width))
+ (when photo-height
+ (list :photo_height photo-height))
+ (when need-name
+ (list :need_name need-name))
+ (when need-phone-number
+ (list :need_phone_number need-phone-number))
+ (when need-email
+ (list :need_email need-email))
+ (when need-shipping-address
+ (list :need_shipping_address need-shipping-address))
+ (when is-flexible
+ (list :is_flexible is-flexible))
+ (when disable-notification
+ (list :disable_notification disable-notification))
+ (when reply-to-message-id
+ (list :reply_to_message_id reply-to-message-id))
+ (when reply-markup
+ (list :reply_markup reply-markup)))))
(apply #'make-request b "sendInvoice" options)))
(defun answer-shipping-query (b shipping-query-id ok &key shipping-options error-message)
"https://core.telegram.org/bots/api#answershippingquery"
(let ((options
- (list
- (cons :shipping_query_id shipping-query-id)
- (cons :ok ok))))
- (when shipping-options (nconc options `((:shipping_options . ,shipping-options))))
- (when error-message (nconc options `((:error_message . ,error-message))))
+ (append
+ (list
+ :shipping_query_id shipping-query-id
+ :ok ok)
+ (when shipping-options
+ (list :shipping_options shipping-options))
+ (when error-message
+ (list :error_message error-message)))))
(apply #'make-request b "answerShippingQuery" options)))
-(defun answer-pre-checkout-query (b pre-checkout-query-id ok &key error-message)
- "https://core.telegram.org/bots/api#answerprecheckoutquery"
+(-> answer-pre-checkout-query (bot pre-checkout-query &key (:error-message string)))
+
+(defun answer-pre-checkout-query (bot pre-checkout-query &key error-message)
+ "If ERROR-MESSAGE argument was given, then response considered is not OK and transaction will be cancelled.
+
+ https://core.telegram.org/bots/api#answerprecheckoutquery"
(let ((options
- (list
- (cons :pre_checkout_query_id pre-checkout-query-id)
- (cons :ok ok))))
- (when error-message (nconc options `((:error_message . ,error-message))))
- (apply #'make-request b "answerPreCheckoutQuery" options)))
+ (append
+ (list
+ :pre_checkout_query_id (pre-checkout-query-id pre-checkout-query)
+ :ok (not error-message))
+ (when error-message
+ (list :error_message error-message)))))
+ (apply #'make-request bot
+ "answerPreCheckoutQuery" options)))
+
+
+(defgeneric make-pre-checkout-query (bot data)
+ (:documentation "Called when user starts payment process.
+
+Parses data like this:
+
+(:|pre_checkout_query|
+ (:|invoice_payload| \"foo-bar-payload\"
+ :|total_amount| 12000
+ :|currency| \"RUB\"
+ :|from|
+ (:|is_premium| T :|language_code| \"en\" :|username| \"svetlyak40wt\"
+ :|last_name| \"svetlyak40wt\" :|first_name| \"Alexander ƛrtemenko\" :|is_bot|
+ NIL :|id| 76226374)
+ :|id| \"327389787349253259\")
+ :|update_id| 7764933)
+")
+ (:method ((bot t) (data t))
+ (let ((id (getf data :|id|))
+ (invoice-payload (getf data :|invoice_payload|))
+ (total-amount (getf data :|total_amount|))
+ (currency (getf data :|currency|))
+ (from (make-user-from-raw (getf data :|from|))))
+ (make-instance 'pre-checkout-query
+ :id id
+ :invoice-payload invoice-payload
+ :total-amount total-amount
+ :currency currency
+ :user from
+ :raw-data data))))
+
+
+(defgeneric make-successful-payment (bot data)
+ (:method ((bot t) (data t))
+ (let ((invoice-payload (getf data :|invoice_payload|))
+ (total-amount (getf data :|total_amount|))
+ (currency (getf data :|currency|))
+ (shipping-option-id (getf data :|shipping_option_id|))
+ (telegram-payment-charge-id (getf data :|telegram_payment_charge_id|))
+ (provider-payment-charge-id (getf data :|provider_payment_charge_id|)))
+ (make-instance 'successful-payment
+ :invoice-payload invoice-payload
+ :total-amount total-amount
+ :currency currency
+ :shipping-option-id shipping-option-id
+ :telegram-payment-charge-id telegram-payment-charge-id
+ :provider-payment-charge-id provider-payment-charge-id
+ :raw-data data))))
+
+
+(defmethod get-user-info ((payload pre-checkout-query))
+ (pre-checkout-query-user payload))
+
+
+(defgeneric on-pre-checkout-query (bot query)
+ (:documentation "Called when user enters payment method credentials and hit \"Pay\" button. Second argument is an object of PRE-CHECKOUT-QUERY type.
+
+ A method should respond with with a call to ANSWER-PRE-CHECKOUT-QUERY function.")
+ (:method ((bot t) (query t))
+ ;; Doing nothing
+ (log:warn "There is no ON-PRE-CHECKOUT-QUERY method for" bot)
+ (values)))
+
+
+(defmethod process ((bot t) (payload pre-checkout-query))
+ ""
+ (log:debug "Processing pre-checkout-query" payload)
+
+ (let ((*current-bot* bot)
+ (*current-message* payload))
+ (handler-case
+ (on-pre-checkout-query bot payload)
+ (cl-telegram-bot/response-processing:interrupt-processing (condition)
+ (declare (ignore condition))
+ (log:debug "Interrupting pre-checkout-query processing" payload))))
+ (values))
diff --git a/src/pipeline.lisp b/src/pipeline.lisp
index ea02f4c..c117694 100644
--- a/src/pipeline.lisp
+++ b/src/pipeline.lisp
@@ -21,4 +21,6 @@
(defmethod process (bot object)
"By default, processing does nothing"
(declare (ignorable bot object))
+ (log:warn "No PROCESS method for processing objects of ~A type."
+ (type-of object))
(values))
diff --git a/src/response.lisp b/src/response.lisp
index 7c19f73..a02e026 100644
--- a/src/response.lisp
+++ b/src/response.lisp
@@ -16,6 +16,9 @@
#:answer-callback-query)
(:import-from #:cl-telegram-bot/markup
#:to-markup)
+ (:import-from #:alexandria
+ #:removef
+ #:remove-from-plistf)
(:export #:response-text
#:reply
#:notify
@@ -57,6 +60,9 @@
:reader url-to-open)))
+(defvar *reply-immediately* t
+ "This variable will be set to NIL when REPLY function is called inside the async flow. Otherwise flow will hang because of non-local exit from the step.")
+
(defun reply (text
&rest args
@@ -68,7 +74,7 @@
disable-notification
reply-to-message-id
reply-markup
- (immediately t))
+ (immediately *reply-immediately*))
(declare (ignorable parse-mode
disable-web-page-preview
disable-notification
@@ -83,6 +89,8 @@
(when reply-markup
(setf (getf args :reply-markup)
(to-markup reply-markup)))
+
+ (remove-from-plistf args :immediately)
(process-response *current-bot*
*current-message*
diff --git a/src/update.lisp b/src/update.lisp
index 86b0348..ea76e97 100644
--- a/src/update.lisp
+++ b/src/update.lisp
@@ -13,6 +13,20 @@
#:process)
(:import-from #:cl-telegram-bot/callback
#:make-callback)
+ (:import-from #:anaphora
+ #:it
+ #:acond)
+ (:import-from #:cl-telegram-bot/envelope
+ #:edited-message
+ #:channel-post
+ #:edited-channel-post)
+ (:import-from #:cl-telegram-bot/chat
+ #:get-chat)
+ (:import-from #:cl-telegram-bot/payments
+ #:make-successful-payment
+ #:make-pre-checkout-query)
+ (:import-from #:cl-telegram-bot/user
+ #:get-user-info)
(:export #:make-update
#:get-raw-data
#:get-update-id
@@ -31,40 +45,41 @@
:reader get-raw-data)))
-(defclass callback-query (update)
- ())
-
-
(defun make-update (data)
- (cond
- ((getf data :|message|)
- (let ((message-data (getf data :|message|)))
- (make-instance 'update
- :id (getf data :|update_id|)
- :payload (make-message message-data)
- :raw-data data)))
- ((getf data :|callback_query|)
- (let* ((callback-data (getf data :|callback_query|))
- ;; (callback-id (getf query :|id|))
- ;; (callback-data (getf query :|data|))
- ;; (message-data (getf query :|message|))
- )
- (make-instance 'callback-query
- :id (getf data :|update_id|)
- :payload (make-callback *current-bot*
- callback-data
- ;; callback-id
- ;; callback-data
- ;; (make-message message-data)
- )
- :raw-data data)))
- (t
- (log:warn "Received not supported update"
- data)
- (make-instance 'update
- :id (getf data :|update_id|)
- :payload nil
- :raw-data data))))
+ (let ((update-id (getf data :|update_id|))
+ (payload
+ (acond
+ ((getf data :|message|)
+ (cond
+ ((getf it :|successful_payment|))
+ (t
+ (make-message it))))
+ ((getf data :|edited_message|)
+ (make-instance 'edited-message
+ :message (make-message it)))
+ ((getf data :|channel_post|)
+ (make-instance 'channel-post
+ :message (make-message it)))
+ ((getf data :|edited_channel_post|)
+ (make-instance 'edited-channel-post
+ :message (make-message it)))
+ ((getf data :|callback_query|)
+ (make-callback *current-bot*
+ it))
+ ((getf data :|pre_checkout_query|)
+ (make-pre-checkout-query *current-bot*
+ it))
+ ((getf data :|successful_payment|)
+ (make-successful-payment *current-bot*
+ it))
+ (t
+ (log:warn "Received not supported update type"
+ data)
+ nil))))
+ (make-instance 'update
+ :id update-id
+ :payload payload
+ :raw-data data)))
(defun get-updates (bot &key limit timeout)
@@ -123,3 +138,12 @@
(log:debug "Processing update" update)
(let ((payload (get-payload update)))
(process bot payload)))
+
+
+
+(defmethod get-chat ((update update))
+ (get-chat (get-payload update)))
+
+
+(defmethod get-user-info ((update update))
+ (get-user-info (get-payload update)))
diff --git a/src/user.lisp b/src/user.lisp
new file mode 100644
index 0000000..e45d477
--- /dev/null
+++ b/src/user.lisp
@@ -0,0 +1,113 @@
+(uiop:define-package #:cl-telegram-bot/user
+ (:use #:cl)
+ (:import-from #:cl-telegram-bot/utils
+ #:api-response-to-plist)
+ (:import-from #:cl-telegram-bot/bot
+ #:bot-info
+ #:bot)
+ (:import-from #:cl-telegram-bot/network
+ #:make-request)
+ (:import-from #:serapeum
+ #:->)
+ (:export #:user
+ #:user-id
+ #:username
+ #:is-premium
+ #:language-code
+ #:first-name
+ #:bot-p
+ #:can-connect-to-business-p
+ #:supports-inline-queries-p
+ #:can-read-all-group-messages-p
+ #:can-join-groups-p
+ #:raw-data
+ #:get-me
+ #:last-name
+ #:get-user-info))
+(in-package #:cl-telegram-bot/user)
+
+
+(defclass user ()
+ ((id :initarg :id
+ :type integer
+ :reader user-id)
+ (username :initarg :username
+ :type (or null string)
+ :initform nil
+ :reader username)
+ (first-name :initarg :first-name
+ :type string
+ :reader first-name)
+ (last-name :initarg :last-name
+ :type (or null string)
+ :initform nil
+ :reader last-name)
+ (language-code :initarg :language-code
+ :type (or null string)
+ :initform nil
+ :reader language-code)
+ (is-premium :initarg :is-premium
+ :type boolean
+ :initform nil
+ :reader is-premium)
+ (is-bot :initarg :is-bot
+ :type boolean
+ :reader bot-p)
+ (can-connect-to-business :initarg :can-connect-to-business
+ :type boolean
+ :initform nil
+ :reader can-connect-to-business-p)
+ (supports-inline-queries :initarg :supports-inline-queries
+ :type boolean
+ :initform nil
+ :reader supports-inline-queries-p)
+ (can-read-all-group-messages :initarg :can-read-all-group-messages
+ :type boolean
+ :initform nil
+ :reader can-read-all-group-messages-p)
+ (can-join-groups :initarg :can-join-groups
+ :type boolean
+ :initform nil
+ :reader can-join-groups-p)
+ (raw-data :initarg :raw-data
+ :reader raw-data)))
+
+
+(defmethod print-object ((user user) stream)
+ (print-unreadable-object (user stream :type t)
+ (format stream "~S @~A"
+ (first-name user)
+ (username user))))
+
+
+(defun make-user-from-raw (raw-data)
+ (let ((initargs (api-response-to-plist raw-data)))
+ (apply #'make-instance
+ 'user
+ :raw-data raw-data
+ initargs)))
+
+
+(-> get-me (bot)
+ (values user &optional))
+
+(defun get-me (bot)
+ "https://core.telegram.org/bots/api#getme"
+ (make-user-from-raw
+ (make-request bot "getMe")))
+
+
+(defmethod bot-info :around ((bot bot))
+ (unless (slot-value bot 'bot-info)
+ (setf (slot-value bot 'bot-info)
+ (get-me bot)))
+ (call-next-method))
+
+
+
+(defgeneric get-user-info (obj)
+ (:documentation "Returns a USER object related to the object.
+
+ If object is not bound to a user, then NIL should be returned.")
+ (:method ((obj t))
+ (values nil)))
diff --git a/src/utils.lisp b/src/utils.lisp
index 7be8534..43e33d3 100644
--- a/src/utils.lisp
+++ b/src/utils.lisp
@@ -1,16 +1,25 @@
(uiop:define-package #:cl-telegram-bot/utils
(:use #:cl)
+ (:import-from #:str)
(:import-from #:arrows
#:->)
+ (:import-from #:serapeum
+ #:collecting
+ #:soft-list-of)
(:import-from #:cl-ppcre
#:regex-replace)
(:import-from #:cl-strings
#:replace-all)
(:import-from #:kebab
#:to-snake-case)
+ (:import-from #:alexandria
+ #:positive-fixnum
+ #:proper-list)
(:export #:make-keyword
- #:obfuscate))
+ #:obfuscate
+ #:api-response-to-plist
+ #:split-by-lines))
(in-package cl-telegram-bot/utils)
@@ -35,3 +44,50 @@
(alexandria:make-keyword)))
+(serapeum:-> api-response-to-plist (proper-list)
+ (values proper-list &optional))
+
+(defun api-response-to-plist (plist)
+ "Transforms a plist with keys like :|foo_bar| into a plist with keys like :foo-bar.
+
+ This can be useful to pass data into CL object contructors."
+ (loop for (key value) on plist by #'cddr
+ append (list (-> key
+ (symbol-name)
+ (make-keyword))
+ value)))
+
+(serapeum:-> split-by-lines (string &key
+ (:max-size positive-fixnum)
+ (:trim-whitespaces-p boolean))
+ (values (soft-list-of string)))
+
+(defun split-by-lines (text &key (max-size 4096) (trim-whitespaces-p t))
+ (flet ((trim-if-needed (text)
+ (if trim-whitespaces-p
+ (str:trim text)
+ text)))
+ (declare (dynamic-extent #'trim-if-needed))
+
+ (collecting
+ (loop with start-at = 0
+ with end-at = 0
+ for char across text
+ for pos upfrom 0
+ when (char= char #\Newline)
+ do (cond
+ ((<= (- pos start-at)
+ max-size)
+ (setf end-at pos))
+ (t
+ (collect
+ (trim-if-needed
+ (subseq text start-at
+ (1+ end-at))))
+ (setf start-at
+ (1+ end-at))
+ (setf end-at
+ pos)))
+ finally (collect
+ (trim-if-needed
+ (subseq text start-at)))))))