telegram-bot-simple 0.7 → 0.8
raw patch · 6 files changed
+179/−16 lines, 6 files
Files
- CHANGELOG.md +15/−0
- examples/TodoBot.hs +1/−0
- src/Telegram/Bot/API/Forum.hs +91/−0
- src/Telegram/Bot/API/Methods.hs +11/−1
- src/Telegram/Bot/API/Types.hs +60/−14
- telegram-bot-simple.cabal +1/−1
CHANGELOG.md view
@@ -1,3 +1,18 @@+0.8+---++- Make package complaing with Telegram Bot API 6.4 (breaking changes included) (see [#134](https://github.com/fizruk/telegram-bot-simple/pull/134)):++ - `ReplyKeyboardMarkup` extended with `is_persistent`;+ - `Message` extended with `has_media_spoiler`;+ - `Chat` extended with `has_hidden_members` and `has_aggressive_anti_spam_enabled`;+ - `editForumTopic` method: fields `name` and `icon_custom_emoji_id` become optional;+ - `has_spoiler` field added to:+ - data constructors: `InputMediaPhoto`, `InputMediaVideo` and `InputMediaAnimation`;+ - method: `sendPhoto`, `sendVideo` and `sendAnimation`;+ - Following message events added `ForumTopicEdited`, `GeneralForumTopicHidden` and `GeneralForumTopicUnhidden`;+ - General topic forum management, i.e. added following functions: `editGeneralForumTopic`, `closeGeneralForumTopic`, `reopenGeneralForumTopic`, `hideGeneralForumTopic` and `unhideGeneralForumTopic`;+ 0.7 ---
examples/TodoBot.hs view
@@ -107,6 +107,7 @@ , replyKeyboardMarkupOneTimeKeyboard = Just True , replyKeyboardMarkupSelective = Nothing , replyKeyboardMarkupInputFieldSelector = Nothing+ , replyKeyboardMarkupIsPersistent = Nothing } addItem :: Item -> Model -> Model
src/Telegram/Bot/API/Forum.hs view
@@ -150,3 +150,94 @@ -- | Use this method to clear the list of pinned messages in a forum topic. The bot must be an administrator in the chat for this to work and must have the @can_pin_messages@ administrator right in the supergroup. Returns 'True' on success. unpinAllForumTopicMessages :: UnpinAllForumTopicMessagesRequest -> ClientM (Response Bool) unpinAllForumTopicMessages = client (Proxy @UnpinAllForumTopicMessages)++-- ** 'editGeneralForumTopic'++data EditGeneralForumTopicRequest = EditGeneralForumTopicRequest+ { editGeneralForumTopicRequestChatId :: SomeChatId -- ^ Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername).+ , editGeneralForumTopicRequestName :: Text -- ^ New topic name, 1-128 characters.+ }+ deriving Generic++instance ToJSON EditGeneralForumTopicRequest where toJSON = gtoJSON++type EditGeneralForumTopic+ = "editGeneralForumTopic"+ :> ReqBody '[JSON] EditGeneralForumTopicRequest+ :> Post '[JSON] (Response Bool)++-- | Use this method to edit the name of the @General@ topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have @can_manage_topics@ administrator rights. Returns 'True' on success.+editGeneralForumTopic :: EditGeneralForumTopicRequest -> ClientM (Response Bool)+editGeneralForumTopic = client (Proxy @EditGeneralForumTopic)++-- ** 'closeGeneralForumTopic'++data CloseGeneralForumTopicRequest = CloseGeneralForumTopicRequest+ { closeGeneralForumTopicChatId :: SomeChatId -- ^ Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername).+ }+ deriving Generic++instance ToJSON CloseGeneralForumTopicRequest where toJSON = gtoJSON++type CloseGeneralForumTopic+ = "closeGeneralForumTopic"+ :> ReqBody '[JSON] CloseGeneralForumTopicRequest+ :> Post '[JSON] (Response Bool)++-- | Use this method to close an open @General@ topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the @can_manage_topics@ administrator rights. Returns 'True' on success.+closeGeneralForumTopic :: CloseGeneralForumTopicRequest -> ClientM (Response Bool)+closeGeneralForumTopic = client (Proxy @CloseGeneralForumTopic)++-- ** 'reopenGeneralForumTopic'++data ReopenGeneralForumTopicRequest = ReopenGeneralForumTopicRequest+ { reopenGeneralForumTopicRequestChatId :: SomeChatId -- ^ Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername).+ }+ deriving Generic++instance ToJSON ReopenGeneralForumTopicRequest where toJSON = gtoJSON++type ReopenGeneralForumTopic+ = "reopenGeneralForumTopic"+ :> ReqBody '[JSON] ReopenGeneralForumTopicRequest+ :> Post '[JSON] (Response Bool)++-- | Use this method to reopen a closed @General@ topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the @can_manage_topics@ administrator rights. The topic will be automatically unhidden if it was hidden. Returns 'True' on success.+reopenGeneralForumTopic :: ReopenGeneralForumTopicRequest -> ClientM (Response Bool)+reopenGeneralForumTopic = client (Proxy @ReopenGeneralForumTopic)++-- ** 'hideGeneralForumTopic'++data HideGeneralForumTopicRequest = HideGeneralForumTopicRequest+ { hideGeneralForumTopicRequestChatId :: SomeChatId -- ^ Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername).+ }+ deriving Generic++instance ToJSON HideGeneralForumTopicRequest where toJSON = gtoJSON++type HideGeneralForumTopic+ = "hideGeneralForumTopic"+ :> ReqBody '[JSON] HideGeneralForumTopicRequest+ :> Post '[JSON] (Response Bool)++-- | Use this method to hide the @General@ topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the @can_manage_topics@ administrator rights. The topic will be automatically closed if it was open. Returns 'True' on success.+hideGeneralForumTopic :: HideGeneralForumTopicRequest -> ClientM (Response Bool)+hideGeneralForumTopic = client (Proxy @HideGeneralForumTopic)++-- ** 'unhideGeneralForumTopic'++data UnhideGeneralForumTopicRequest = UnhideGeneralForumTopicRequest+ { unhideGeneralForumTopicRequestChatId :: SomeChatId -- ^ Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)+ }+ deriving Generic++instance ToJSON UnhideGeneralForumTopicRequest where toJSON = gtoJSON++type UnhideGeneralForumTopic+ = "unhideGeneralForumTopic"+ :> ReqBody '[JSON] UnhideGeneralForumTopicRequest+ :> Post '[JSON] (Response Bool)++-- | Use this method to unhide the @General@ topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the @can_manage_topics@ administrator rights. Returns 'True' on success.+unhideGeneralForumTopic :: UnhideGeneralForumTopicRequest -> ClientM (Response Bool)+unhideGeneralForumTopic = client (Proxy @UnhideGeneralForumTopic)
src/Telegram/Bot/API/Methods.hs view
@@ -276,6 +276,7 @@ , sendPhotoCaption :: Maybe Text -- ^ Photo caption (may also be used when resending Photos by file_id), 0-1024 characters after entities parsing , sendPhotoParseMode :: Maybe ParseMode -- ^ Send 'MarkdownV2', 'HTML' or 'Markdown' (legacy), if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your bot's message. , sendPhotoCaptionEntities :: Maybe [MessageEntity] -- ^ A JSON-serialized list of special entities that appear in the caption, which can be specified instead of /parse_mode/.+ , sendPhotoHasSpoiler :: Maybe Bool -- ^ Pass 'True' if the photo needs to be covered with a spoiler animation. , sendPhotoDisableNotification :: Maybe Bool -- ^ Sends the message silently. Users will receive a notification with no sound. , sendPhotoProtectContent :: Maybe Bool -- ^ Protects the contents of the sent message from forwarding and saving. , sendPhotoReplyToMessageId :: Maybe MessageId -- ^ If the message is a reply, ID of the original message.@@ -297,6 +298,7 @@ $ (maybe id (\t -> ((Input "caption" t):)) sendPhotoCaption) $ (maybe id (\t -> ((Input "parse_mode" (TL.toStrict . TL.replace "\"" "" $ encodeToLazyText t)):)) sendPhotoParseMode) $ (maybe id (\t -> ((Input "caption_entities" (TL.toStrict $ encodeToLazyText t)):)) sendPhotoCaptionEntities)+ $ (maybe id (\t -> ((Input "has_spoiler" (bool "false" "true" t)):)) sendPhotoHasSpoiler) $ (maybe id (\t -> ((Input "disable_notification" (bool "false" "true" t)):)) sendPhotoDisableNotification) $ (maybe id (\t -> ((Input "protect_content" (bool "false" "true" t)):)) sendPhotoProtectContent) $ (maybe id (\t -> ((Input "reply_to_message_id" (TL.toStrict $ encodeToLazyText t)):)) sendPhotoReplyToMessageId)@@ -439,6 +441,7 @@ , sendVideoCaption :: Maybe Text -- ^ Video caption (may also be used when resending videos by file_id), 0-1024 characters after entities parsing , sendVideoParseMode :: Maybe ParseMode -- ^ Send 'MarkdownV2', 'HTML' or 'Markdown' (legacy), if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your bot's message. , sendVideoCaptionEntities :: Maybe [MessageEntity] -- ^ A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode+ , sendVideoHasSpoiler :: Maybe Bool -- ^ Pass 'True' if the video needs to be covered with a spoiler animation. , sendVideoSupportsStreaming :: Maybe Bool -- ^ Pass True, if the uploaded video is suitable for streaming , sendVideoDisableNotification :: Maybe Bool -- ^ Sends the message silently. Users will receive a notification with no sound. , sendVideoProtectContent :: Maybe Bool -- ^ Protects the contents of the sent message from forwarding and saving.@@ -468,6 +471,8 @@ \t -> Input "parse_mode" (TL.toStrict . TL.replace "\"" "" $ encodeToLazyText t) , sendVideoCaptionEntities <&> \t -> Input "caption_entities" (TL.toStrict $ encodeToLazyText t)+ , sendVideoHasSpoiler <&>+ \t -> Input "has_spoiler" (bool "false" "true" t) , sendVideoDuration <&> \t -> Input "duration" (TL.toStrict $ encodeToLazyText t) , sendVideoWidth <&>@@ -526,6 +531,7 @@ , sendAnimationCaption :: Maybe Text -- ^ Animation caption (may also be used when resending animation by file_id), 0-1024 characters after entities parsing , sendAnimationParseMode :: Maybe ParseMode -- ^ Send 'MarkdownV2', 'HTML' or 'Markdown' (legacy), if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your bot's message. , sendAnimationCaptionEntities :: Maybe [MessageEntity] -- ^ A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode+ , sendAnimationHasSpoiler :: Maybe Bool -- ^ Pass 'True' if the animation needs to be covered with a spoiler animation. , sendAnimationDisableNotification :: Maybe Bool -- ^ Sends the message silently. Users will receive a notification with no sound. , sendAnimationProtectContent :: Maybe Bool -- ^ Protects the contents of the sent message from forwarding and saving. , sendAnimationReplyToMessageId :: Maybe MessageId -- ^ If the message is a reply, ID of the original message.@@ -554,6 +560,8 @@ \t -> Input "parse_mode" (TL.toStrict . TL.replace "\"" "" $ encodeToLazyText t) , sendAnimationCaptionEntities <&> \t -> Input "caption_entities" (TL.toStrict $ encodeToLazyText t)+ , sendAnimationHasSpoiler <&>+ \t -> Input "has_spoiler" (bool "false" "true" t) , sendAnimationDuration <&> \t -> Input "duration" (TL.toStrict $ encodeToLazyText t) , sendAnimationWidth <&>@@ -882,6 +890,7 @@ type SendChatAction = "sendChatAction" :> RequiredQueryParam "chat_id" SomeChatId+ :> QueryParam "message_thread_id" MessageThreadId :> RequiredQueryParam "action" Text :> Post '[JSON] (Response Bool) @@ -902,7 +911,8 @@ -- We only recommend using this method when a -- response from the bot will take a noticeable -- amount of time to arrive.-sendChatAction :: SomeChatId -- ^ Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)+sendChatAction :: SomeChatId -- ^ Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername).+ -> Maybe MessageThreadId -- ^ Unique identifier for the target message thread; supergroups only. -> Text -- ^ Type of action to broadcast. Choose one, depending on what the user is about to receive: typing for text messages, upload_photo for photos, record_video or upload_video for videos, record_voice or upload_voice for voice notes, upload_document for general files, choose_sticker for stickers, find_location for location data, record_video_note or upload_video_note for video notes. -> ClientM (Response Bool) sendChatAction = client (Proxy @SendChatAction)
src/Telegram/Bot/API/Types.hs view
@@ -157,7 +157,8 @@ , messageVideoNote :: Maybe VideoNote -- ^ Message is a video note, information about the video message , messageVoice :: Maybe Voice -- ^ Message is a voice message, information about the file , messageCaption :: Maybe Text -- ^ Caption for the audio, document, photo, video or voice, 0-200 characters- , messageCaptionEntities :: Maybe [MessageEntity] -- ^ For messages with a caption, special entities like usernames, URLs, bot commands, etc. that appear in the caption+ , messageCaptionEntities :: Maybe [MessageEntity] -- ^ For messages with a caption, special entities like usernames, URLs, bot commands, etc. that appear in the caption.+ , messageHasMediaSpoiler :: Maybe Bool -- ^ 'True', if the message media is covered by a spoiler animation. , messageContact :: Maybe Contact -- ^ Message is a shared contact, information about the contact , messageDice :: Maybe Dice -- ^ Message is a dice with random value. , messageGame :: Maybe Game -- ^ Message is a game, information about the game. More about games » , messageLocation :: Maybe Location -- ^ Message is a shared location, information about the location@@ -173,6 +174,8 @@ , messageSupergroupChatCreated :: Maybe Bool -- ^ Service message: the supergroup has been created. This field can‘t be received in a message coming through updates, because bot can’t be a member of a supergroup when it is created. It can only be found in reply_to_message if someone replies to a very first message in a directly created supergroup. , messageChannelChatCreated :: Maybe Bool -- ^ Service message: the channel has been created. This field can‘t be received in a message coming through updates, because bot can’t be a member of a channel when it is created. It can only be found in reply_to_message if someone replies to a very first message in a channel. , messageAutoDeleteTimerChanged :: Maybe MessageAutoDeleteTimerChanged -- ^ Service message: auto-delete timer settings changed in the chat.+ , messageHasAggressiveAntiSpamEnabled :: Maybe Bool -- ^ 'True', if aggressive anti-spam checks are enabled in the supergroup. The field is only available to chat administrators. Returned only in 'getChat'.+ , messageHasHiddenMembers :: Maybe Bool -- ^ 'True', if non-administrators can only get the list of bots and administrators in the chat. Returned only in 'getChat'. , messageMigrateToChatId :: Maybe ChatId -- ^ The group has been migrated to a supergroup with the specified identifier. This number may be greater than 32 bits and some programming languages may have difficulty/silent defects in interpreting it. But it is smaller than 52 bits, so a signed 64 bit integer or double-precision float type are safe for storing this identifier. , messageMigrateFromChatId :: Maybe ChatId -- ^ The supergroup has been migrated from a group with the specified identifier. This number may be greater than 32 bits and some programming languages may have difficulty/silent defects in interpreting it. But it is smaller than 52 bits, so a signed 64 bit integer or double-precision float type are safe for storing this identifier. , messagePinnedMessage :: Maybe Message -- ^ Specified message was pinned. Note that the Message object in this field will not contain further reply_to_message fields even if it is itself a reply.@@ -197,11 +200,14 @@ newtype MessageId = MessageId Integer deriving (Eq, Show, ToJSON, FromJSON, Hashable) +instance ToHttpApiData MessageId where+ toUrlPiece a = pack . show @Integer $ coerce a+ -- | Unique identifier of a message thread to which the message belongs; for supergroups only. newtype MessageThreadId = MessageThreadId Integer deriving (Eq, Show, ToJSON, FromJSON, Hashable) -instance ToHttpApiData MessageId where+instance ToHttpApiData MessageThreadId where toUrlPiece a = pack . show @Integer $ coerce a -- | The unique identifier of a media message group a message belongs to.@@ -472,11 +478,33 @@ newtype ForumTopicClosed = ForumTopicClosed Object deriving (Generic, Show) +-- ** 'ForumTopicEdited'++-- | This object represents a service message about an edited forum topic.+data ForumTopicEdited = ForumTopicEdited+ { forumTopicEditedName :: Maybe Text -- ^ New name of the topic, if it was edited.+ , forumTopicEditedIconCustomEmojiId :: Maybe Text -- ^ New identifier of the custom emoji shown as the topic icon, if it was edited; an empty string if the icon was removed.+ }+ deriving (Generic, Show)+ -- ** 'ForumTopicReopened' +-- | This object represents a service message about a forum topic reopened in the chat. Currently holds no information. newtype ForumTopicReopened = ForumTopicReopened Object deriving (Generic, Show) +-- ** 'GeneralForumTopicHidden'++-- | This object represents a service message about General forum topic hidden in the chat. Currently holds no information.+newtype GeneralForumTopicHidden = GeneralForumTopicHidden Object+ deriving (Generic, Show)++-- ** 'GeneralForumTopicUnhidden'++-- | This object represents a service message about General forum topic unhidden in the chat. Currently holds no information.+newtype GeneralForumTopicUnhidden = GeneralForumTopicUnhidden Object+ deriving (Generic, Show)+ -- ** 'VideoChatScheduled' -- | This object represents a service message about a video chat scheduled in the chat.@@ -559,7 +587,8 @@ -- | This object represents a custom keyboard with reply options (see Introduction to bots for details and examples). data ReplyKeyboardMarkup = ReplyKeyboardMarkup- { replyKeyboardMarkupKeyboard :: [[KeyboardButton]] -- ^ Array of button rows, each represented by an Array of KeyboardButton objects+ { replyKeyboardMarkupKeyboard :: [[KeyboardButton]] -- ^ Array of button rows, each represented by an Array of KeyboardButton objects.+ , replyKeyboardMarkupIsPersistent :: Maybe Bool -- ^ Requests clients to always show the keyboard when the regular keyboard is hidden. Defaults to 'False', in which case the custom keyboard can be hidden and opened with a keyboard icon. , replyKeyboardMarkupResizeKeyboard :: Maybe Bool -- ^ Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard smaller if there are just two rows of buttons). Defaults to false, in which case the custom keyboard is always of the same height as the app's standard keyboard. , replyKeyboardMarkupOneTimeKeyboard :: Maybe Bool -- ^ Requests clients to hide the keyboard as soon as it's been used. The keyboard will still be available, but clients will automatically display the usual letter-keyboard in the chat – the user can press a special button in the input field to see the custom keyboard again. Defaults to false. , replyKeyboardMarkupInputFieldSelector :: Maybe Text -- ^ The placeholder to be shown in the input field when the keyboard is active; 1-64 characters.@@ -1252,19 +1281,24 @@ } data InputMedia- = InputMediaPhoto InputMediaGeneric -- ^ Represents a photo to be sent.+ = InputMediaPhoto -- ^ Represents a photo to be sent.+ { inputMediaPhotoGeneric :: InputMediaGeneric+ , inputMediaPhotoHasSpoiler :: Maybe Bool -- ^ Pass 'True' if the video needs to be covered with a spoiler animation.+ } | InputMediaVideo -- ^ Represents a video to be sent. { inputMediaVideoGeneric :: InputMediaGenericThumb , inputMediaVideoWidth :: Maybe Integer -- ^ Video width , inputMediaVideoHeight :: Maybe Integer -- ^ Video height , inputMediaVideoDuration :: Maybe Integer -- ^ Video duration in seconds- , inputMediaVideoSupportsStreaming :: Maybe Bool -- ^ Pass True, if the uploaded video is suitable for streaming+ , inputMediaVideoSupportsStreaming :: Maybe Bool -- ^ Pass 'True', if the uploaded video is suitable for streaming.+ , inputMediaVideoHasSpoiler :: Maybe Bool -- ^ Pass 'True' if the video needs to be covered with a spoiler animation. } | InputMediaAnimation -- ^ Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. { inputMediaAnimationGeneric :: InputMediaGenericThumb , inputMediaAnimationWidth :: Maybe Integer -- ^ Animation width , inputMediaAnimationHeight :: Maybe Integer -- ^ Animation height , inputMediaAnimationDuration :: Maybe Integer -- ^ Animation duration in seconds+ , inputMediaAnimationHasSpoiler :: Maybe Bool -- ^ Pass 'True' if the video needs to be covered with a spoiler animation. } | InputMediaAudio -- ^ Represents an audio file to be treated as music to be sent. { inputMediaAudioGeneric :: InputMediaGenericThumb@@ -1315,8 +1349,11 @@ , ''PollOption , ''MessageAutoDeleteTimerChanged , ''ForumTopicCreated+ , ''ForumTopicEdited , ''ForumTopicClosed , ''ForumTopicReopened+ , ''GeneralForumTopicHidden+ , ''GeneralForumTopicUnhidden , ''Invoice , ''SuccessfulPayment , ''OrderInfo@@ -1379,22 +1416,24 @@ instance ToJSON InputMedia where toJSON = \case- InputMediaPhoto img ->- addJsonFields (toJSON img) (addType "photo" [])- InputMediaVideo imgt width height duration streaming ->+ InputMediaPhoto img spoiler ->+ addJsonFields (toJSON img) (addType "photo" [ "has_spoiler" .= spoiler])+ InputMediaVideo imgt width height duration streaming spoiler -> addJsonFields (toJSON imgt) (addType "video" [ "width" .= width , "height" .= height , "duration" .= duration , "support_streaming" .= streaming+ , "has_spoiler" .= spoiler ])- InputMediaAnimation imgt width height duration ->+ InputMediaAnimation imgt width height duration spoiler -> addJsonFields (toJSON imgt) (addType "animation" [ "width" .= width , "height" .= height , "duration" .= duration+ , "has_spoiler" .= spoiler ]) InputMediaAudio imgt duration performer title -> addJsonFields (toJSON imgt)@@ -1412,11 +1451,14 @@ instance ToMultipart Tmp InputMedia where toMultipart = let in \case- InputMediaPhoto img ->+ InputMediaPhoto img spoiler -> addMultipartFields- [ Input "type" "photo"- ] (toMultipart img)- InputMediaVideo imgt width height duration streaming ->+ (Input "type" "photo"+ : catMaybes+ [ spoiler <&>+ \t -> Input "has_spoiler" (bool "false" "true" t)+ ]) (toMultipart img)+ InputMediaVideo imgt width height duration streaming spoiler -> addMultipartFields (Input "type" "video" : catMaybes @@ -1428,8 +1470,10 @@ \t -> Input "duration" (TL.toStrict $ encodeToLazyText t) , streaming <&> \t -> Input "support_streaming" (bool "false" "true" t)+ , spoiler <&>+ \t -> Input "has_spoiler" (bool "false" "true" t) ]) (toMultipart imgt)- InputMediaAnimation imgt width height duration ->+ InputMediaAnimation imgt width height duration spoiler -> addMultipartFields (Input "type" "animation" : catMaybes @@ -1439,6 +1483,8 @@ \t -> Input "height" (TL.toStrict $ encodeToLazyText t) , duration <&> \t -> Input "duration" (TL.toStrict $ encodeToLazyText t)+ , spoiler <&>+ \t -> Input "has_spoiler" (bool "false" "true" t) ]) (toMultipart imgt) InputMediaAudio imgt duration performer title -> addMultipartFields
telegram-bot-simple.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: telegram-bot-simple-version: 0.7+version: 0.8 synopsis: Easy to use library for building Telegram bots. description: Please see the README on Github at <https://github.com/fizruk/telegram-bot-simple#readme> category: Web