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] [![](https://github-actions.40ants.com/40ants/cl-telegram-bot/matrix.svg?only=ci.run-tests)][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)))))))