From c71ace5e8efe06c9b2683c502b03e3e10c5a2318 Mon Sep 17 00:00:00 2001 From: Marco Aceti Date: Tue, 26 Mar 2019 16:53:28 +0100 Subject: [PATCH] Update master to release 0.6 Update master to release 0.6 --- AUTHORS | 1 + Dockerfile | 8 +- README.md | 6 +- botogram/components.py | 3 +- botogram/objects/__init__.py | 6 +- botogram/objects/chats.py | 75 ++++++- botogram/objects/media.py | 17 ++ botogram/objects/messages.py | 7 +- botogram/objects/mixins.py | 186 ++++++++++++++--- docs/_templates/links.html | 6 +- docs/api/telegram.rst | 382 +++++++++++++++++++++++++++++++++-- docs/buildthedocs.yml | 10 + docs/changelog/0.6.rst | 98 ++++++++- docs/changelog/index.rst | 1 + docs/conf.py | 6 +- netlify.toml | 27 --- setup.py | 4 +- 17 files changed, 742 insertions(+), 101 deletions(-) delete mode 100644 netlify.toml diff --git a/AUTHORS b/AUTHORS index d731d8e..e0bd5ae 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,3 +15,4 @@ Contributors: Fernando Possebon Ilya Otyutskiy Stefano Teodorani + Francesco Zimbolo diff --git a/Dockerfile b/Dockerfile index c764016..a59b6bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ # Image to build doc FROM python:3.6-alpine3.6 as BUILDER RUN apk update \ - && apk add git bash make + && apk add git bash make RUN pip install invoke virtualenv COPY ./requirements-docs.txt /requirements-docs.txt RUN pip install -r /requirements-docs.txt @@ -13,9 +13,9 @@ RUN cd /botogram && invoke docs && cd .netlify && make # Image final FROM nginx:latest -ARG botogram_version=dev -ENV env_botogram_version=$botogram_version RUN rm /etc/nginx/conf.d/default.conf COPY /nginx-doc.conf /etc/nginx/conf.d/default.conf -RUN sed 's/RELEASE/'"$env_botogram_version"'/g' -i /etc/nginx/conf.d/default.conf COPY --from=BUILDER /botogram/.netlify/build/ ./botogram +ARG botogram_version=dev +ENV env_botogram_version=$botogram_version +RUN sed 's/RELEASE/'"$env_botogram_version"'/g' -i /etc/nginx/conf.d/default.conf diff --git a/README.md b/README.md index 798f1b7..f10e293 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ get all the news about botogram in its [Telegram channel][channel]. > Please note botogram currently doesn't support some of the upstream API > features. All of them will be implemented in botogram 1.0 -**Supported Python versions**: 3.4, 3.5 +**Supported Python versions**: 3.4+ **License**: MIT ### Installation @@ -37,13 +37,13 @@ get all the news about botogram in its [Telegram channel][channel]. You can install easily botogram with pip (be sure to have Python 3.4 or higher installed): - $ python3 -m pip install botogram + $ python3 -m pip install botogram2 If you want to install from the source code, you can clone the repository and install it with setuptools. Be sure to have Python 3.4 (or a newer version), pip, virtualenv, setuptools and [invoke][3] installed: - $ git clone https://github.com/pietroalbini/botogram.git + $ git clone https://github.com/python-botogram/botogram.git $ cd botogram $ invoke install diff --git a/botogram/components.py b/botogram/components.py index 250cd00..9f2a58d 100644 --- a/botogram/components.py +++ b/botogram/components.py @@ -50,8 +50,7 @@ def __new__(cls, *args, **kwargs): self._component_id = str(uuid.uuid4()) - if cls.component_name is None: - self.component_name = cls.__name__ + self.component_name = str() return self diff --git a/botogram/objects/__init__.py b/botogram/objects/__init__.py index f94dfd3..82767a5 100644 --- a/botogram/objects/__init__.py +++ b/botogram/objects/__init__.py @@ -22,11 +22,11 @@ from .chats import User, Chat, UserProfilePhotos, Permissions from .media import PhotoSize, Photo, Audio, Voice, Document, Sticker, \ - Video, Contact, Location, Venue + Video, VideoNote, Contact, Location, Venue from .messages import Message from .markup import ReplyKeyboardMarkup, ReplyKeyboardHide, ForceReply from .updates import Update, Updates - +from .mixins import Album __all__ = [ # Chats-related objects @@ -42,9 +42,11 @@ "Document", "Sticker", "Video", + "VideoNote", "Contact", "Location", "Venue", + "Album", # Messages-related objects "Message", diff --git a/botogram/objects/chats.py b/botogram/objects/chats.py index ffa194c..062f80c 100644 --- a/botogram/objects/chats.py +++ b/botogram/objects/chats.py @@ -23,8 +23,6 @@ from . import mixins from datetime import datetime as dt from time import mktime - - from .media import Photo @@ -41,7 +39,11 @@ class User(BaseObject, mixins.ChatMixin): optional = { "last_name": str, "username": str, - "is_bot": bool + "language_code": str, + "is_bot": bool, + } + replace_keys = { + "language_code": "lang", } _check_equality_ = "id" @@ -114,6 +116,9 @@ class Chat(BaseObject, mixins.ChatMixin): "sticker_set_name": str, "can_set_sticker_set": bool } + replace_keys = { + "invite_link": "_invite_link", + } _check_equality_ = "id" def _to_user(self): @@ -294,6 +299,70 @@ def kick(self, user, time=None): def permissions(self, user): return Permissions(user, self) + def set_description(self, description=""): + if self.type != "private": + """Set the new chat description. Leave empty to delete it.""" + if len(description) <= 255: + self._api.call("setChatDescription", { + "chat_id": self.id, + "description": description + }, expect=bool) + else: + raise ValueError("The new description must be below 255 characters.") + else: + raise RuntimeError("This method works only with non-private chats.") + + @mixins._require_api + def revoke_invite_link(self): + """Revoke and generate a new invike link for this chat""" + if self.type not in ("supergroup", "channel"): + raise RuntimeError("You can revoke the invite link only in a supergroup or a channel") + + link = self._api.call("exportChatInviteLink", { + "chat_id": self.id, + }).get('result', None) + self._cache_invite_link = link + return link + + @property + @mixins._require_api + def invite_link(self): + """Get the invite link of this chat""" + if self.type not in ("supergroup", "channel"): + raise RuntimeError("You can get the invite link only in a supergroup or a channel") + + if hasattr(self, "_cache_invite_link"): + return self._cache_invite_link + + chat = self._api.call("getChat", { + "chat_id": self.id, + }, expect=Chat) + if not chat._invite_link: + return self.revoke_invite_link() + + self._cache_invite_link = chat._invite_link + return self._cache_invite_link + + def pin_message(self, message, notify=True): + """Pin a message""" + # Check if the chat is a supergroup + if self.type not in ("supergroup", "channel"): + raise RuntimeError("This chat is nota a supergroup or channel!") + + if isinstance(message, Message): + message = message.id + + return self._api.call("pinChatMessage", { + "chat_id": self.id, + "message_id": message, + "disable_notification": not notify + }, expect=bool) + + def unpin_message(self): + return self._api.call("unpinChatMessage", { + "chat_id": self.id, + }, expect=bool) + class Permissions: def __init__(self, user, chat): diff --git a/botogram/objects/media.py b/botogram/objects/media.py index bd0f115..1375c33 100644 --- a/botogram/objects/media.py +++ b/botogram/objects/media.py @@ -239,3 +239,20 @@ class Venue(BaseObject): "foursquare_id": "foursquare", } _check_equality_ = "location" + + +class VideoNote(BaseObject, mixins.FileMixin): + """Telegram API representation of a VideoNote + + https://core.telegram.org/bots/api#videonote + """ + required = { + "file_id": str, + "length": int, + "duration": int, + } + optional = { + "thumb": PhotoSize, + "file_size": int, + } + _check_equality_ = "file_id" diff --git a/botogram/objects/messages.py b/botogram/objects/messages.py index af7e61c..1778eec 100644 --- a/botogram/objects/messages.py +++ b/botogram/objects/messages.py @@ -24,8 +24,8 @@ from . import mixins from .. import utils from .chats import User, Chat -from .media import Audio, Voice, Document, Photo, Sticker, Video, Contact, \ - Location, Venue +from .media import Audio, Voice, Document, Photo, Sticker, Video, VideoNote, \ + Contact, Location, Venue _url_protocol_re = re.compile(r"^https?:\/\/|s?ftp:\/\/|mailto:", re.I) @@ -343,6 +343,7 @@ def from_(self): "photo": Photo, "sticker": Sticker, "video": Video, + "video_note": VideoNote, "caption": str, "contact": Contact, "location": Location, @@ -418,7 +419,7 @@ def left_chat_participant(self): return self.left_chat_member @property - @utils.deprecated("Message.message_id", "0.5", + @utils.deprecated("Message.message_id", "0.6", "Rename property to Message.id") def message_id(self): return self.id diff --git a/botogram/objects/mixins.py b/botogram/objects/mixins.py index 7958882..c23064e 100644 --- a/botogram/objects/mixins.py +++ b/botogram/objects/mixins.py @@ -21,10 +21,10 @@ import importlib import json -from .. import utils from .. import syntaxes +from .. import utils from ..utils.deprecations import _deprecated_message - +from .base import multiple _objects_module = None @@ -55,7 +55,7 @@ class ChatMixin: def _get_call_args(self, reply_to, extra, attach, notify): """Get default API call arguments""" # Convert instance of Message to ids in reply_to - if hasattr(reply_to, "message_id"): + if hasattr(reply_to, "id"): reply_to = reply_to.id args = {"chat_id": self.id} @@ -93,16 +93,15 @@ def send(self, message, preview=True, reply_to=None, syntax=None, @_require_api def send_photo(self, path=None, file_id=None, url=None, caption=None, - reply_to=None, extra=None, attach=None, notify=True, *, - syntax=None): + syntax=None, reply_to=None, extra=None, attach=None, + notify=True): """Send a photo""" args = self._get_call_args(reply_to, extra, attach, notify) if caption is not None: args["caption"] = caption - syntax = syntaxes.guess_syntax(caption, syntax) - if syntax is not None: - args["parse_mode"] = syntax - + if syntax is not None: + syntax = syntaxes.guess_syntax(caption, syntax) + args["parse_mode"] = syntax if path is not None and file_id is None and url is None: files = {"photo": open(path, "rb")} elif file_id is not None and path is None and url is None: @@ -129,9 +128,9 @@ def send_audio(self, path=None, file_id=None, url=None, duration=None, args = self._get_call_args(reply_to, extra, attach, notify) if caption is not None: args["caption"] = caption - syntax = syntaxes.guess_syntax(caption, syntax) - if syntax is not None: - args["parse_mode"] = syntax + if syntax is not None: + syntax = syntaxes.guess_syntax(caption, syntax) + args["parse_mode"] = syntax if duration is not None: args["duration"] = duration if performer is not None: @@ -164,6 +163,9 @@ def send_voice(self, path=None, file_id=None, url=None, duration=None, args = self._get_call_args(reply_to, extra, attach, notify) if caption is not None: args["caption"] = caption + if syntax is not None: + syntax = syntaxes.guess_syntax(caption, syntax) + args["parse_mode"] = syntax if duration is not None: args["duration"] = duration syntax = syntaxes.guess_syntax(caption, syntax) @@ -189,18 +191,19 @@ def send_voice(self, path=None, file_id=None, url=None, duration=None, @_require_api def send_video(self, path=None, file_id=None, url=None, - duration=None, caption=None, reply_to=None, extra=None, - attach=None, notify=True, *, syntax=None): + duration=None, caption=None, streaming=True, + reply_to=None, extra=None, attach=None, + notify=True, *, syntax=None): """Send a video""" args = self._get_call_args(reply_to, extra, attach, notify) + args["supports_streaming"] = streaming if duration is not None: args["duration"] = duration if caption is not None: args["caption"] = caption - syntax = syntaxes.guess_syntax(caption, syntax) - if syntax is not None: - args["parse_mode"] = syntax - + if syntax is not None: + syntax = syntaxes.guess_syntax(caption, syntax) + args["parse_mode"] = syntax if path is not None and file_id is None and url is None: files = {"video": open(path, "rb")} elif file_id is not None and path is None and url is None: @@ -218,6 +221,30 @@ def send_video(self, path=None, file_id=None, url=None, return self._api.call("sendVideo", args, files, expect=_objects().Message) + @_require_api + def send_video_note(self, path=None, file_id=None, duration=None, + diameter=None, reply_to=None, extra=None, + attach=None, notify=True): + """Send a video note""" + args = self._get_call_args(reply_to, extra, attach, notify) + if duration is not None: + args["duration"] = duration + if diameter is not None: + args["length"] = diameter + if path is not None and file_id is None: + files = {"video_note": open(path, "rb")} + elif file_id is not None and path is None: + files = None + args["video_note"] = file_id + elif path is None and file_id is None: + raise TypeError("Path or file_id or URL is missing") + else: + raise TypeError("Only one among path and file_id must be" + + "passed") + + return self._api.call("sendVideoNote", args, files, + expect=_objects().Message) + @_require_api def send_file(self, path=None, file_id=None, url=None, reply_to=None, extra=None, attach=None, notify=True, caption=None, *, @@ -226,10 +253,9 @@ def send_file(self, path=None, file_id=None, url=None, reply_to=None, args = self._get_call_args(reply_to, extra, attach, notify) if caption is not None: args["caption"] = caption - syntax = syntaxes.guess_syntax(caption, syntax) - if syntax is not None: - args["parse_mode"] = syntax - + if syntax is not None: + syntax = syntaxes.guess_syntax(caption, syntax) + args["parse_mode"] = syntax if path is not None and file_id is None and url is None: files = {"document": open(path, "rb")} elif file_id is not None and path is None and url is None: @@ -328,6 +354,17 @@ def delete_message(self, message): "message_id": message, }) + @_require_api + def send_album(self, album=None, reply_to=None, notify=True): + """Send a Album""" + albums = SendAlbum(self, reply_to, notify) + if album is not None: + albums._content = album._content + albums._file = album._file + albums._used = True + return albums.send() + return albums + class MessageMixin: """Add some methods for messages""" @@ -379,7 +416,7 @@ def edit(self, text, syntax=None, preview=True, extra=None, attach=None): @_require_api def edit_caption(self, caption, extra=None, attach=None, *, syntax=None): """Edit this message's caption""" - args = {"message_id": self.message_id, "chat_id": self.chat.id} + args = {"message_id": self.id, "chat_id": self.chat.id} args["caption"] = caption syntax = syntaxes.guess_syntax(caption, syntax) if syntax is not None: @@ -403,7 +440,7 @@ def edit_caption(self, caption, extra=None, attach=None, *, syntax=None): @_require_api def edit_attach(self, attach): """Edit this message's attachment""" - args = {"message_id": self.message_id, "chat_id": self.chat.id} + args = {"message_id": self.id, "chat_id": self.chat.id} args["reply_markup"] = attach self._api.call("editMessageReplyMarkup", args) @@ -433,6 +470,11 @@ def reply_with_video(self, *args, **kwargs): """Reply with a video to the current message""" return self.chat.send_video(*args, reply_to=self, **kwargs) + @_require_api + def reply_with_video_note(self, *args, **kwargs): + """Reply with a video note to the current message""" + return self.chat.send_video_note(*args, reply_to=self, **kwargs) + @_require_api def reply_with_file(self, *args, **kwargs): """Reply with a generic file to the current chat""" @@ -458,6 +500,11 @@ def reply_with_contact(self, *args, **kwargs): """Reply with a contact to the current message""" return self.chat.send_contact(*args, reply_to=self, **kwargs) + @_require_api + def reply_with_album(self, *args, **kwargs): + """Reply with an album to the current message""" + return self.chat.send_album(*args, reply_to=self, **kwargs) + @_require_api def delete(self): """Delete the message""" @@ -479,3 +526,94 @@ def save(self, path): downloaded = self._api.file_content(response["result"]["file_path"]) with open(path, 'wb') as f: f.write(downloaded) + + +class Album: + """Factory for albums""" + def __init__(self): + self._content = [] + self._file = [] + + def add_photo(self, path=None, url=None, file_id=None, caption=None, + syntax=None): + """Add a photo the the album instance""" + args = {"type": "photo"} + if caption is not None: + args["caption"] = caption + if syntax is not None: + syntax = syntaxes.guess_syntax(caption, syntax) + args["parse_mode"] = syntax + if path is not None and file_id is None and url is None: + name = "photo" + str(len(self._file)) + args["media"] = "attach://" + name + self._file.append((name, (path, open(path, "rb")))) + elif file_id is not None and path is None and url is None: + args["media"] = file_id + elif url is not None and file_id is None and path is None: + args["media"] = url + elif path is None and file_id is None and url is None: + raise TypeError("path or file_id or URL is missing") + else: + raise TypeError("Only one among path, file_id and URL must be" + + "passed") + + self._content.append(args) + + def add_video(self, path=None, file_id=None, url=None, duration=None, + caption=None, syntax=None): + """Add a video the the album instance""" + args = {"type": "video"} + if duration is not None: + args["duration"] = duration + if caption is not None: + args["caption"] = caption + if syntax is not None: + syntax = syntaxes.guess_syntax(caption, syntax) + args["parse_mode"] = syntax + if path is not None and file_id is None and url is None: + name = "photo" + str(len(self._file)) + args["media"] = "attach://" + name + self._file.append((name, (path, open(path, "rb")))) + elif file_id is not None and path is None and url is None: + args["media"] = file_id + elif url is not None and file_id is None and path is None: + args["media"] = url + elif path is None and file_id is None and url is None: + raise TypeError("path or file_id or URL is missing") + else: + raise TypeError("Only one among path, file_id and URL must be" + + "passed") + + self._content.append(args) + + +class SendAlbum(Album): + """Send the album instance to the chat passed as argument""" + def __init__(self, chat, reply_to=None, notify=True): + super(SendAlbum, self).__init__() + self._get_call_args = chat._get_call_args + self._api = chat._api + self.reply_to = reply_to + self.notify = notify + self._used = False + + def __enter__(self): + self._used = True + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + self.send() + + def send(self): + """Send the Album to telgram""" + args = self._get_call_args(self.reply_to, None, None, self.notify) + args["media"] = json.dumps(self._content) + return self._api.call("sendMediaGroup", args, self._file, + expect=multiple(_objects().Message)) + + def __del__(self): + if not self._used: + utils.warn(1, "error_with_album", + "you should use `with` to use send_album\ + -- check the documentation") diff --git a/docs/_templates/links.html b/docs/_templates/links.html index b2eea58..b77049a 100644 --- a/docs/_templates/links.html +++ b/docs/_templates/links.html @@ -4,13 +4,13 @@

Some useful links

  • Micro website
  • -
  • +
  • Download @ PyPI
  • -
  • +
  • Source code @ GitHub
  • -
  • +
  • Issue tracker @ GitHub
  • diff --git a/docs/api/telegram.rst b/docs/api/telegram.rst index cfcc149..e056c9a 100644 --- a/docs/api/telegram.rst +++ b/docs/api/telegram.rst @@ -25,6 +25,7 @@ about its business. * :py:class:`~botogram.Document` * :py:class:`~botogram.Sticker` * :py:class:`~botogram.Video` +* :py:class:`~botogram.VideoNote` * :py:class:`~botogram.Voice` * :py:class:`~botogram.Contact` * :py:class:`~botogram.Location` @@ -61,6 +62,14 @@ about its business. *This attribute can be None if it's not provided by Telegram.* + .. py:attribute:: lang + + The user's language, given by Telegram. + + *This attribute can be None if it's not provided by Telegram* + + .. versionadded:: 0.6 + .. py:attribute:: name The computed name of the user. If someone has only the first name, this @@ -73,8 +82,9 @@ about its business. .. py:attribute:: is_bot - is_bot indicates if the user is a bot. due to the telegram privacy rules, + This attribute indicates if the user is a bot. Due to the telegram privacy rules, this can be true only when your bot can actually see other bots' messages. + .. versionadded:: 0.2 .. py:attribute:: avatar @@ -130,7 +140,7 @@ about its business. Now the method returns the sent message - .. py:method:: send_photo([path=None, file_id=None, url=None, caption=None, reply_to=None, extra=None, attach=None, notify=True, syntax=None]) + .. py:method:: send_photo([path=None, file_id=None, url=None, caption=None, syntax=None, reply_to=None, extra=None, attach=None, notify=True]) Send a photo to the user. You can specify the photo by passing its *path*, its *url*, or its Telegram *file_id*. Only one of these arguments must be passed. @@ -146,10 +156,11 @@ about its business. The *notify* parameter is for defining if your message should trigger a notification on the client side (yes by default). - :param str path: The path to the photo. + :param str path: The path of the photo. :param str file_id: The Telegram *file_id* of the photo. - :param str url: The URL to the photo. + :param str url: The URL of the photo. :param str caption: A caption for the photo. + :param str syntax: The name of the syntax used for the caption. :param int reply_to: The ID of the :py:class:`~botogram.Message` this one is replying to. :param object attach: An extra thing to attach to the message. :param object extra: An extra reply interface object to attach. @@ -174,6 +185,10 @@ about its business. Support text formatting in caption through *syntax*. + .. versionchanged:: 0.6 + + Added support for syntax + .. py:method:: send_audio([path=None, file_id=None, url=None, duration=None, performer=None, title=None, reply_to=None, attach=None, extra=None, notify=True, caption=None, syntax=None]) Send an audio track to the user. You can specify the track by passing its *path*, @@ -220,6 +235,10 @@ about its business. Support text formatting in caption through *syntax*. + .. versionchanged:: 0.6 + + Added support for syntax + .. py:method:: send_voice([path=None, file_id=None, url=None, duration=None, reply_to=None, extra=None, attach=None, notify=True, caption=None, syntax=None]) Send a voice message to the user. You can specify the audio by passing its *path*, @@ -259,18 +278,18 @@ about its business. .. versionchanged:: 0.5 Added support for *caption*, *file_id* and *url*. - + .. versionchanged:: 0.6 - - Support text formatting in caption through *syntax*. - .. py:method:: send_video([path=None, file_id=None, url=None, duration=None, caption=None, reply_to=None, attach=None, extra=None, notify=True, syntax=None]) + Added support for syntax + + .. py:method:: send_video([path=None, file_id=None, url=None, duration=None, caption=None, streaming=True, reply_to=None, attach=None, extra=None, notify=True, syntax=None]) Send a video to the user. You can specify the video by passing its *path*, its *url*, or its Telegram *file_id*. Only one of these arguments must be passed. You may optionally specify the *duration* and the *caption* of the video. - If the audio track you're sending is in reply to another message, + If the video you're sending is in reply to another message, set *reply_to* to the ID of the other :py:class:`~botogram.Message`. The *attach* parameter allows you to attach extra things like @@ -284,6 +303,7 @@ about its business. :param str url: The URL to the video :param int duration: The video duration, in seconds :param str caption: The caption of the video + :param bool streaming: Pass `True` or `False` to set whether the video should support streaming or not. Defaults as `True`. :param int reply_to: The ID of the :py:class:`~botogram.Message` this one is replying to :param object attach: An extra thing to attach to the message. :param object extra: An extra reply interface object to attach @@ -306,7 +326,39 @@ about its business. .. versionchanged:: 0.6 - Support text formatting in caption through *syntax*. + Added support for syntax + + .. py:method:: send_video_note([path=None, file_id=None, duration=None, diameter=None, reply_to=None, attach=None, extra=None, notify=True]) + + Send a video note to the user. You can specify the video note by passing its *path*, + or its Telegram *file_id*. Only one of these arguments must be passed. + + You may optionally specify the *duration* and the *diameter* of the video. + If the video note track you're sending is in reply to another message, + set *reply_to* to the ID of the other :py:class:`~botogram.Message`. + + The *attach* parameter allows you to attach extra things like + :ref:`buttons ` to the message. + + The *notify* parameter is for defining if your message should trigger + a notification on the client side (yes by default). + + :param str path: The path to the video + :param str file_id: The Telegram *file_id* of the video + :param int duration: The video duration, in seconds + :param str diameter: the video diameter + :param int reply_to: The ID of the :py:class:`~botogram.Message` this one is replying to + :param object attach: An extra thing to attach to the message. + :param object extra: An extra reply interface object to attach + :param bool notify: If you want to trigger the client notification. + :returns: The message you sent + :rtype: ~botogram.Message + + .. deprecated:: 0.4 + + The *extra* parameter is now deprecated + + .. versionadded:: 0.6 .. py:method:: send_file([path=None, file_id=None, url=None, reply_to=None, attach=None, extra=None, notify=True, caption=None, syntax=None]) @@ -323,8 +375,8 @@ about its business. a notification on the client side (yes by default). :param str path: The path to the file - :param str file_id: The Telegram *file_id* of the video - :param str url: The URL to the video + :param str file_id: The Telegram *file_id* of the file + :param str url: The URL to the file :param int reply_to: The ID of the :py:class:`~botogram.Message` this one is replying to :param object attach: An extra thing to attach to the message. :param object extra: An extra reply interface object to attach @@ -350,6 +402,10 @@ about its business. Support text formatting in caption through *syntax*. + .. versionchanged:: 0.6 + + Added support for syntax + .. py:method:: send_location(latitude, longitude, [reply_to=None, attach=None, extra=None, notify=True]) Send the geographic location to the user. If the location you're sending @@ -486,6 +542,39 @@ about its business. .. versionadded:: 0.3 + .. py:method:: send_album([album=None, reply_to=None, notify=True]) + + Send album to the chat. This method returns an instance of :py:class:`~botogram.Album` or sends the :py:class:`~botogram.Album` provided by the album variable. If the + message you are sending is in reply to another, set *reply_to* to the ID + of the other :py:class:`~botogram.Message`. + The *notify* parameter defines if your message should + trigger the notification on the client side (yes by default). + + + .. code-block:: python + @bot.command("my_cats") + def my_cats(chat): + album = botogram.Album() + album.add_photo('tiger.jpg', caption='Tiger, the father', syntax='HTML') + album.add_photo(url='https://http.cat/100.jpg', caption='Simba, the cat-mother of the year!') + album.add_photo(file_id='some file ID here', caption='...and Sassy the daughter') + chat.send_album(album) + + @bot.command("my_dogs") + def my_dogs(chat): + with chat.send_album() as album: + album.add_video('spank.mp4', caption='A video of Spank digging holes in our garden :(') + album.add_photo('shilla.jpg', caption='Shilla is so jealous!') + + :param album: The :py:class:`~botogram.Album` send to the chat + :param int reply_to: The ID of the :py:class:`~botogram.Message` this one is replying to. + :param bool notify: If you want to trigger a notification on the client + + :returns: The messages you sent + :rtype: list of :py:class:`~botogram.Message` + + .. versionadded:: 0.6 + .. py:method:: delete_message(message) Delete the message with the provided ID or :py:class:`~botogram.Message` object. @@ -551,7 +640,9 @@ about its business. .. py:attribute:: invite_link - This group chat/channel invite link. + The chat invite link, it works only in a supergroup or a channel. + You can revoke the current link and create a new one by using the + :py:method:`~botogram.Chat.revoke_invite_link` method. *This attribute can be None if it's not provided by Telegram.* .. versionadded:: 0.6 @@ -831,6 +922,33 @@ about its business. :rtype: :py:class:`~botogram.Permissions` .. versionadded:: 0.6 + .. py:method:: set_description([description='']) + + Edit or remove the chat description. + + Your bot must be an administrator of the chat with the appropriate admin rights in order for this method to work. + Doesn't work with private chats. + + :param str description: The new chat description (leave empty to remove it) + .. versionadded:: 0.6 + + .. py:method:: revoke_invite_link() + Revokes the previous invite link of the chat and returns a new one. + It works only in a supergroup or a channel. + + .. code-block:: python + + @bot.command("revoke") + def revoke_invite(chat): + chat.send("Alright, revoking the invite link...") + new_link = chat.revoke_invite_link() + chat.send("New link: " + new_link) + + + :returns: A new invite link + :rtype: str + .. versionadded:: 0.6 + .. py:method:: send(message, [preview=True, reply_to=None, syntax=None, attach=None, extra=None, notify=True]) Send the textual *message* to the chat. You may optionally stop clients @@ -999,7 +1117,7 @@ about its business. Support text formatting in caption through *syntax*. - .. py:method:: send_video([path=None, file_id=None, url=None, duration=None, caption=None, reply_to=None, extra=None, attach=None, notify=True, syntax=None]) + .. py:method:: send_video([path=None, file_id=None, url=None, duration=None, caption=None, streaming=True, reply_to=None, extra=None, attach=None, notify=True, syntax=None]) Send a video to the chat. You can specify the video by passing its *path*, its *url*, or its Telegram *file_id*. Only one of these arguments must be passed. @@ -1020,6 +1138,7 @@ about its business. :param str url: The URL to the video :param int duration: The video duration, in seconds :param str caption: The caption of the video + :param bool streaming: Pass `True` or `False` to set whether the video should support streaming or not. Defaults as `True`. :param int reply_to: The ID of the :py:class:`~botogram.Message` this one is replying to :param object attach: An extra thing to attach to the message. :param object extra: An extra reply interface object to attach @@ -1044,6 +1163,38 @@ about its business. Support text formatting in caption through *syntax*. + .. py:method:: send_video_note([path=None, file_id=None, duration=None, diameter=None, reply_to=None, attach=None, extra=None, notify=True]) + + Send a video note to the user. You can specify the video note by passing its *path*, + or its Telegram *file_id*. Only one of these arguments must be passed. + + You may optionally specify the *duration* and the *diameter* of the video. + If the video note track you're sending is in reply to another message, + set *reply_to* to the ID of the other :py:class:`~botogram.Message`. + + The *attach* parameter allows you to attach extra things like + :ref:`buttons ` to the message. + + The *notify* parameter is for defining if your message should trigger + a notification on the client side (yes by default). + + :param str path: The path to the video + :param str file_id: The Telegram *file_id* of the video + :param int duration: The video duration, in seconds + :param str diameter: the video diameter + :param int reply_to: The ID of the :py:class:`~botogram.Message` this one is replying to + :param object attach: An extra thing to attach to the message. + :param object extra: An extra reply interface object to attach + :param bool notify: If you want to trigger the client notification. + :returns: The message you sent + :rtype: ~botogram.Message + + .. deprecated:: 0.4 + + The *extra* parameter is now deprecated + + .. versionadded:: 0.6 + .. py:method:: send_file([path=None, file_id=None, url=None, reply_to=None, attach=None, extra=None, notify=True, caption=None, syntax=None]) Send a generic file to the chat. You can specify the video by passing its *path*, @@ -1216,6 +1367,39 @@ about its business. .. versionadded:: 0.3 + .. py:method:: send_album([album=None, reply_to=None, notify=True]) + + Send album to the chat. This method returns an instance of :py:class:`~botogram.Album` or sends the :py:class:`~botogram.Album` provided by the album variable. If the + message you are sending is in reply to another, set *reply_to* to the ID + of the other :py:class:`~botogram.Message`. + The *notify* parameter defines if your message should + trigger the notification on the client side (yes by default). + + + .. code-block:: python + @bot.command("my_cats") + def my_cats(chat): + album = botogram.Album() + album.add_photo('tiger.jpg', caption='Tiger, the father', syntax='HTML') + album.add_photo(url='https://http.cat/100.jpg', caption='Simba, the cat-mother of the year!') + album.add_photo(file_id='some file ID here', caption='...and Sassy the daughter') + chat.send_album(album) + + @bot.command("my_dogs") + def my_dogs(chat): + with chat.send_album() as album: + album.add_video('spank.mp4', caption='A video of Spank digging holes in our garden :(') + album.add_photo('shilla.jpg', caption='Shilla is so jealous!') + + :param album: The :py:class:`~botogram.Album` send to the chat + :param int reply_to: The ID of the :py:class:`~botogram.Message` this one is replying to. + :param bool notify: If you want to trigger a notification on the client + + :returns: The messages you sent + :rtype: list of :py:class:`~botogram.Message` + + .. versionadded:: 0.6 + .. py:method:: delete_message(message) Delete the message with the provided ID or :py:class:`~botogram.Message` object. @@ -1226,6 +1410,25 @@ about its business. .. versionadded:: 0.4 + .. py:method:: pin_message(message[, notify=True]) + + Pin the message with the provided ID or :py:class:`~botogram.Message` object. + A message can be pinned only if it's sent in a supergroup or channel where the bot is an admin. + + The *notify* parameter is for defining if your message should trigger + a notification on the client side (yes by default). + + :param message: The message to delete (can be an ID too) + :param bool notify: If you want to trigger a notification on the client + + .. versionadded:: 0.6 + + .. py:method:: unpin_message() + + Unpin the message pinned + + .. versionadded:: 0.6 + .. py:class:: botogram.ParsedText This class contains the parsed representation of the text of a received @@ -1454,6 +1657,12 @@ about its business. *This attribute can be None if it's not provided by Telegram.* + .. py:attribute:: video_note + + A :py:class:`~botogram.VideoNote` object, for when this message is a video note + file. + + *This attribute can be None if it's not provided by Telegram.* .. py:attribute:: caption A caption for when this message is a photo or video file. @@ -1802,7 +2011,7 @@ about its business. Now the method returns the sent message - .. py:method:: reply_with_video(path, [duration=None, caption=None, attach=None, extra=None, notify=True]) + .. py:method:: reply_with_video(path, [duration=None, caption=None, streaming=True, attach=None, extra=None, notify=True]) Reply with the video found in the *path* to the chat. You may optionally specify the *duration* and the *caption* of the video. @@ -1816,6 +2025,7 @@ about its business. :param str path: The path to the video :param int duration: The video duration, in seconds :param str caption: The caption of the video + :param bool streaming: Pass `True` or `False` to set whether the video should support streaming or not. Defaults as `True`. :param object attach: An extra thing to attach to the message. :param object extra: An extra reply interface object to attach :param bool notify: If you want to trigger the client notification. @@ -1830,6 +2040,38 @@ about its business. Now the method returns the sent message + .. py:method:: reply_with_video_note([path=None, file_id=None, duration=None, length=None, attach=None, extra=None, notify=True]) + + Reply with the video note to the user. You can specify the video note by passing its *path*, + or its Telegram *file_id*. Only one of these arguments must be passed. + + You may optionally specify the *duration* and the *length* of the video. + If the video note track you're sending is in reply to another message, + set *reply_to* to the ID of the other :py:class:`~botogram.Message`. + + The *attach* parameter allows you to attach extra things like + :ref:`buttons ` to the message. + + The *notify* parameter is for defining if your message should trigger + a notification on the client side (yes by default). + + :param str path: The path to the video + :param str file_id: The Telegram *file_id* of the video + :param int duration: The video duration, in seconds + :param str length: The length of the video + :param int reply_to: The ID of the :py:class:`~botogram.Message` this one is replying to + :param object attach: An extra thing to attach to the message. + :param object extra: An extra reply interface object to attach + :param bool notify: If you want to trigger the client notification. + :returns: The message you sent + :rtype: ~botogram.Message + + .. deprecated:: 0.4 + + The *extra* parameter is now deprecated + + .. versionadded:: 0.6 + .. py:method:: reply_with_file(path, [attach=None, extra=None, notify=True]) Reply with the generic file found in the *path* to the chat. If the file @@ -1973,6 +2215,38 @@ about its business. .. versionadded:: 0.3 + .. py:method:: reply_with_album([album=None, notify=True]) + + Send album to the chat. This method returns an instance of :py:class:`~botogram.Album` or sends the :py:class:`~botogram.Album` provided by the album variable. + The *notify* parameter defines if your message should + trigger the notification on the client side (yes by default). + + + .. code-block:: python + @bot.command("my_cats") + def my_cats(message): + album = botogram.Album() + album.add_photo('tiger.jpg', caption='Tiger, the father', syntax='HTML') + album.add_photo(url='https://http.cat/100.jpg', caption='Simba, the cat-mother of the year!') + album.add_photo(file_id='some file ID here', caption='...and Sassy the daughter') + message.reply_with_album(album) + + @bot.command("my_dogs") + def my_dogs(message): + with message.reply_with_album() as album: + album.add_video('spank.mp4', caption='A video of Spank digging holes in our garden :(') + album.add_photo('shilla.jpg', caption='Shilla is so jealous!') + + :param album: The :py:class:`~botogram.Album` send to the chat + + :param bool notify: If you want to trigger a notification on the client + + :returns: The messages you sent + :rtype: list of :py:class:`~botogram.Message` + + .. versionadded:: 0.6 + + .. py:class:: botogram.Photo This class provides a general representation of a photo received by your bot. @@ -2041,7 +2315,8 @@ about its business. Despite its name, objects of this class are also used to describe images of various Telegram API objects, including :py:class:`~botogram.UserProfilePhotos` and thumbnail images for the :py:class:`~botogram.Document`, - :py:class:`~botogram.Sticker`, and :py:class:`~botogram.Video` classes. + :py:class:`~botogram.Sticker`, :py:class:`~botogram.Video` + and :py:class:`~botogram.VideoNote` classes. See the :py:class:`botogram.Photo` class for a more friendly way to work with photos specifically. @@ -2125,7 +2400,8 @@ about its business. This class represents a general file. Other objects of this API may be used instead in order to take advantage of client side features for common file types, such as with :py:class:`~botogram.Audio`, :py:class:`~botogram.Photo`, - :py:class:`~botogram.Video` and :py:class:`~botogram.Voice`. Use this class + :py:class:`~botogram.Video`, :py:class:`~botogram.VideoNote` + and :py:class:`~botogram.Voice`. Use this class when working with all other file types, or for when you do not want clients to offer specialized features for the type. @@ -2252,6 +2528,40 @@ about its business. :param str path: The file name path locating where the video should be saved. +.. py:class:: botogram.VideoNote + + This class represents a video note file. + + .. py:attribute:: file_id + + The string ID of the file. + + .. py:attribute:: length + + The integer length of the video note as defined by the sender. + + .. py:attribute:: thumb + + A :py:class:`~botogram.PhotoSize` object representing a thumbnail image of + the video note as defined by the sender. + + *This attribute can be None if it's not provided by Telegram.* + + .. py:attribute:: file_size + + The integer size of the video note file. + + *This attribute can be None if it's not provided by Telegram.* + + .. py:method:: save(path) + + Save the video note to a file located by *path*. Be aware that Telegram does + not provide the name of the original file sent by its sender. This should + be generated as part of the path. + + :param str path: The file name path locating where the video note should be saved. + + .. py:class:: botogram.Voice This class represents a voice message. @@ -2329,8 +2639,6 @@ about its business. This class represents the permissions of the user. If you use this as a context manager, the save method will automatically be called if no exceptions were raised." - .. versionadded:: 0.6 - .. py:attribute:: until_date The unix timestamp or datime format of when the changes you're doing will be reverted. @@ -2357,6 +2665,7 @@ about its business. This method automatically detects the changes you made and doesn't do anything if no attribute was changed. + .. versionadded:: 0.6 .. py:class:: botogram.Venue @@ -2389,6 +2698,41 @@ about its business. *This value can be None if the venue doesn't have a Foursquare ID.* +.. py:class:: botogram.Album + + + This object represents an album (a group of photos and videos). + + .. py:method:: add_photo([path=None, url=None, file_id=None, caption=None, syntax=None]) + + Add a photo to the album. You can specify the photo by passing its *path*, + its *url*, or its Telegram *file_id*. Only one of these arguments must be passed. + + You may optionally specify a *caption* for the photo being sent. + + :param str path: The path to the photo. + :param str file_id: The Telegram *file_id* of the photo. + :param str url: The URL to the photo. + :param str caption: A caption for the photo. + :param str syntax: The name of the syntax used for the caption. + + .. py:method:: add_video([path=None, file_id=None, url=None, duration=None, caption=None, syntax=None]) + + Add a video to the album. You can specify the video by passing its *path*, + its *url*, or its Telegram *file_id*. Only one of these arguments must be passed. + + You may optionally specify the *duration* and the *caption* of the video. + + :param str path: The path of the video + :param str file_id: The Telegram *file_id* of the video + :param str url: The URL of the video + :param int duration: The video duration, in seconds + :param str caption: The caption of the video + :param str syntax: The name of the syntax used for the caption. + + .. versionadded:: 0.6 + + .. py:class:: botogram.Update This class represents an update received by the bot. You should not need to diff --git a/docs/buildthedocs.yml b/docs/buildthedocs.yml index 9c53222..dcfcb4d 100644 --- a/docs/buildthedocs.yml +++ b/docs/buildthedocs.yml @@ -9,6 +9,16 @@ versions: notice: unstable warning: This documentation is for the unstable and in-development version of botogram! + - name: "0.6" + source: + provider: local + url: . + checkout: v0.6 + directory: docs + title: botogram 0.6 + notice: alpha + warning: null + - name: "0.5" source: diff --git a/docs/changelog/0.6.rst b/docs/changelog/0.6.rst index 07d07aa..3ff4849 100644 --- a/docs/changelog/0.6.rst +++ b/docs/changelog/0.6.rst @@ -1,11 +1,11 @@ .. Copyright (c) 2015-2019 The Botogram Authors (see AUTHORS) - Documentation released under the MIT license (see LICENSE) +Documentation released under the MIT license (see LICENSE) =========================== Changelog of botogram 0.6.x =========================== -Here you can find all the changes in the botogram 0.6.x releases. +Here you can find all the changes in the botogram 0.6.x release. .. _changelog-0.6: @@ -14,7 +14,22 @@ botogram 0.6 *Alpha release, not yet released.* -Release description not yet written. +Since this version the original author and the main maintener `Pietro Albini `_ +left the project to `Matteo Bocci `_ and `Marco Aceti `_. + +We decided to move to a new PyPi package: `botogram2 `_. +`botogram `_ **is now deprecated and no longer maintained**. + +To upgrade to the new version, you can issue those commands: :: + + /path/to/python3 -m pip uninstall botogram + /path/to/python3 -m pip install botogram2 + + +We also moved to a new website `botogram.dev `_ with a new, fresh domain. + +**Old website** `botogram.pietroalbini.org `_ **is now deprecated and no longer maintaned**. + New features ------------ @@ -31,16 +46,87 @@ New features * New argument ``syntax`` in :py:meth:`botogram.User.send_file` * New argument ``syntax`` in :py:meth:`botogram.User.send_video` * New argument ``syntax`` in :py:meth:`botogram.User.send_voice` + * New argument ``syntax`` in :py:meth:`botogram.Message.reply_with_photo` + * New argument ``syntax`` in :py:meth:`botogram.Message.reply_with_audio` + * New argument ``syntax`` in :py:meth:`botogram.Message.reply_with_file` + * New argument ``syntax`` in :py:meth:`botogram.Message.reply_with_video` + * New argument ``syntax`` in :py:meth:`botogram.Message.reply_with_voice` * New argument ``syntax`` in :py:meth:`botogram.Message.edit_caption` - + +* Added support for more administrator tools + + * New :py:class:`botogram.Permissions` class + * New attribute :py:attr:`botogram.Chat.permissions` + * New attribute :py:attr:`botogram.Chat.all_members_are_administrators` + * New attribute :py:attr:`botogram.Chat.description` + * New attribute :py:attr:`botogram.Chat.pinned_message` + * New attribute :py:attr:`botogram.Chat.sticker_set_name` + * New attribute :py:attr:`botogram.Chat.can_set_sticker_set` + * New method :py:meth:`botogram.Chat.kick` + * New method :py:meth:`botogram.Chat.permissions` + * New method :py:meth:`botogram.Chat.set_description` + * New method :py:meth:`botogram.Chat.pin_message` + * New method :py:meth:`botogram.Chat.unpin_message` + +* New class :py:class:`botogram.User` attributes + + * New attribute :py:attr:`~botogram.User.is_bot` + * New attribute :py:attr:`~botogram.User.lang` + * Added support for sending stickers by ID or URL - + * New arguments ``path``, ``file_id`` and ``url`` in :py:meth:`botogram.Chat.send_sticker` * New arguments ``path``, ``file_id`` and ``url`` in :py:meth:`botogram.User.send_sticker` - + +* Added support for albums + + * New class :py:class:`botogram.Album` + * New method :py:meth:`botogram.Chat.send_album` + * New method :py:meth:`botogram.User.send_album` + * New method :py:meth:`botogram.Message.reply_with_album` + +* Added support for pinned messages + + * New method :py:meth:`botogram.Chat.pin_message` + * New method :py:meth:`botogram.Chat.unpin_message` + +* Added support for video streaming + + * New argument ``streaming`` in :py:meth:`botogram.Chat.send_video` + * New argument ``streaming`` in :py:meth:`botogram.User.send_video` + * New argument ``streaming`` in :py:meth:`botogram.Message.reply_with_video` + +* Added support for invite links: + + * New attribute :py:attr:`botogram.Chat.invite_link` + * New method :py:meth:`botogram.Chat.revoke_invite_link` + +* Added support for video notes: + + * New class :py:class:`botogram.VideoNote` + * New attribute :py:attr:`botogram.Message.video_note` + * New method :py:meth:`botogram.Chat.send_video_note` + * New method :py:meth:`botogram.User.send_video_note` + * New method :py:meth:`botogram.Message.reply_with_video_note` + +* New argument ``attach`` in :py:meth:`botogram.Bot.edit_message` + +* New argument ``attach`` in :py:meth:`botogram.Bot.edit_caption` + +* Renamed attribute :py:attr:`botogram.Message.message_id` to :py:attr:`botogram.Message.id` + + + Deprecated features ------------------- Deprecated features will be removed in botogram 1.0! +* The attribute :py:attr:`botogram.Message.message_id` is now deprecated * The ``sticker`` parameter in :py:meth:`botogram.Chat.send_sticker` and :py:meth:`botogram.User.send_sticker` is now deprecated + + +Bug fixes +------------------- + +* Fixed inline callbacks on Windows (`#114 `_) diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index 6f60796..129aa80 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -12,6 +12,7 @@ Here you can see what changed in every botogram release. .. toctree:: :maxdepth: 2 + 0.6 0.5 0.4 0.3 diff --git a/docs/conf.py b/docs/conf.py index dab1dd4..aa5e5a2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,10 +34,10 @@ project = "botogram" copyright = "2015-2019 The Botogram Authors" -author = "Pietro Albini" +author = "Botogram-dev" -version = "0.5" -release = "0.5" +version = "0.6" +release = "0.6" language = None diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index af8d474..0000000 --- a/netlify.toml +++ /dev/null @@ -1,27 +0,0 @@ -[build] -base = ".netlify" -publish = ".netlify/build" -command = "make" - -# Permanent redirect from the netlify domain to the canonical domain -[[redirects]] -from = "https://org-pietroalbini-botogram.netlify.com/*" -to = "https://botogram.pietroalbini.org/:splat" -status = 301 -force = true - -# Redirect to the latest docs -[[redirects]] -from = "/docs/" -to = "/docs/0.5/" -status = 302 - -# Security headers -[[headers]] -for = "/*" -[headers.values] -X-Frame-Options = "DENY" -X-Xss-Protection = "1; mode=block" -X-Content-Type-Options = "nosniff" -Referrer-Policy = "no-referrer" - diff --git a/setup.py b/setup.py index 205ae22..09a6799 100644 --- a/setup.py +++ b/setup.py @@ -36,8 +36,8 @@ def hello_command(chat, message, args): setuptools.setup( - name = "botogram", - version = "0.5", + name = "botogram2", + version = "0.6", url = "https://botogram.dev", license = "MIT",