diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index bcb96ea4..cacd9342 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -506,6 +506,7 @@ def start(): f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",") f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",") f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",") + f.write("\n 0xb0700031: \"pyrogram.client.types.UserStatus\"") f.write("\n}\n") diff --git a/docs/source/pyrogram/Types.rst b/docs/source/pyrogram/Types.rst index e8dc709c..8763c0e1 100644 --- a/docs/source/pyrogram/Types.rst +++ b/docs/source/pyrogram/Types.rst @@ -10,6 +10,7 @@ Users & Chats :nosignatures: User + UserStatus Chat ChatPhoto ChatMember @@ -73,6 +74,9 @@ Input Media .. autoclass:: User :members: +.. autoclass:: UserStatus + :members: + .. autoclass:: Chat :members: diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 6fb6fff4..12f181c3 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -29,12 +29,12 @@ from .api.errors import Error from .client.types import ( Audio, Chat, ChatMember, ChatMembers, ChatPhoto, Contact, Document, InputMediaPhoto, InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact, - Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, Update, User, + Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, Update, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove ) from .client import ( Client, ChatAction, ParseMode, Emoji, MessageHandler, DeletedMessagesHandler, CallbackQueryHandler, - RawUpdateHandler, DisconnectHandler, Filters + RawUpdateHandler, DisconnectHandler, UserStatusHandler, Filters ) diff --git a/pyrogram/client/__init__.py b/pyrogram/client/__init__.py index b345de94..00b9905a 100644 --- a/pyrogram/client/__init__.py +++ b/pyrogram/client/__init__.py @@ -22,5 +22,5 @@ from .filters import Filters from .handlers import ( MessageHandler, DeletedMessagesHandler, CallbackQueryHandler, RawUpdateHandler, - DisconnectHandler + DisconnectHandler, UserStatusHandler ) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 5506cbdd..a0c5e365 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -25,7 +25,7 @@ from threading import Thread import pyrogram from pyrogram.api import types from ..ext import utils -from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler +from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, UserStatusHandler log = logging.getLogger(__name__) @@ -108,6 +108,8 @@ class Dispatcher: callback_query = update.callback_query + user_status = update.user_status + if message and isinstance(handler, MessageHandler): if not handler.check(message): continue @@ -123,6 +125,11 @@ class Dispatcher: continue args = (self.client, callback_query) + elif user_status and isinstance(handler, UserStatusHandler): + if not handler.check(user_status): + continue + + args = (self.client, user_status) else: continue @@ -209,6 +216,14 @@ class Dispatcher: ) ) ) + elif isinstance(update, types.UpdateUserStatus): + self.dispatch( + pyrogram.Update( + user_status=utils.parse_user_status( + update.status, update.user_id + ) + ) + ) else: continue except Exception as e: diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index a497e3c9..c7a10db9 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -129,6 +129,30 @@ def parse_chat_photo(photo): ) +def parse_user_status(user_status, user_id: int = None, is_bot: bool = False) -> pyrogram_types.UserStatus or None: + if is_bot: + return None + + status = pyrogram_types.UserStatus(user_id) + + if isinstance(user_status, types.UserStatusOnline): + status.online = True + status.date = user_status.expires + elif isinstance(user_status, types.UserStatusOffline): + status.offline = True + status.date = user_status.was_online + elif isinstance(user_status, types.UserStatusRecently): + status.recently = True + elif isinstance(user_status, types.UserStatusLastWeek): + status.within_week = True + elif isinstance(user_status, types.UserStatusLastMonth): + status.within_month = True + else: + status.long_time_ago = True + + return status + + def parse_user(user: types.User) -> pyrogram_types.User or None: return pyrogram_types.User( id=user.id, @@ -142,7 +166,8 @@ def parse_user(user: types.User) -> pyrogram_types.User or None: username=user.username, language_code=user.lang_code, phone_number=user.phone, - photo=parse_chat_photo(user.photo) + photo=parse_chat_photo(user.photo), + status=parse_user_status(user.status, is_bot=user.bot), ) if user else None diff --git a/pyrogram/client/handlers/__init__.py b/pyrogram/client/handlers/__init__.py index d06b2a76..ff1ead7a 100644 --- a/pyrogram/client/handlers/__init__.py +++ b/pyrogram/client/handlers/__init__.py @@ -17,7 +17,8 @@ # along with Pyrogram. If not, see . from .callback_query_handler import CallbackQueryHandler +from .deleted_messages_handler import DeletedMessagesHandler from .disconnect_handler import DisconnectHandler from .message_handler import MessageHandler -from .deleted_messages_handler import DeletedMessagesHandler from .raw_update_handler import RawUpdateHandler +from .user_status_handler import UserStatusHandler diff --git a/pyrogram/client/handlers/user_status_handler.py b/pyrogram/client/handlers/user_status_handler.py new file mode 100644 index 00000000..2442d7eb --- /dev/null +++ b/pyrogram/client/handlers/user_status_handler.py @@ -0,0 +1,54 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from .handler import Handler + + +class UserStatusHandler(Handler): + """The UserStatus handler class. Used to handle user status updates (user going online or offline). + It is intended to be used with :meth:`add_handler() ` + + For a nicer way to register this handler, have a look at the + :meth:`on_user_status() ` decorator. + + Args: + callback (``callable``): + Pass a function that will be called when a new UserStatus update arrives. It takes *(client, user_status)* + as positional arguments (look at the section below for a detailed description). + + filters (:obj:`Filters `): + Pass one or more filters to allow only a subset of messages to be passed + in your callback function. + + Other parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the user status handler. + + user_status (:obj:`UserStatus `): + The received UserStatus update. + """ + + def __init__(self, callback: callable, filters=None): + super().__init__(callback, filters) + + def check(self, user_status): + return ( + self.filters(user_status) + if callable(self.filters) + else True + ) diff --git a/pyrogram/client/methods/decorators/__init__.py b/pyrogram/client/methods/decorators/__init__.py index f84a922c..6cf9940a 100644 --- a/pyrogram/client/methods/decorators/__init__.py +++ b/pyrogram/client/methods/decorators/__init__.py @@ -17,11 +17,19 @@ # along with Pyrogram. If not, see . from .on_callback_query import OnCallbackQuery +from .on_deleted_messages import OnDeletedMessages from .on_disconnect import OnDisconnect from .on_message import OnMessage -from .on_deleted_messages import OnDeletedMessages from .on_raw_update import OnRawUpdate +from .on_user_status import OnUserStatus -class Decorators(OnMessage, OnDeletedMessages, OnCallbackQuery, OnRawUpdate, OnDisconnect): +class Decorators( + OnMessage, + OnDeletedMessages, + OnCallbackQuery, + OnRawUpdate, + OnDisconnect, + OnUserStatus +): pass diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py new file mode 100644 index 00000000..81367c3e --- /dev/null +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -0,0 +1,41 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from ...ext import BaseClient + + +class OnUserStatus(BaseClient): + def on_user_status(self, filters=None, group: int = 0): + """Use this decorator to automatically register a function for handling + user status updates. This does the same thing as :meth:`add_handler` using the + :class:`UserStatusHandler`. + + Args: + filters (:obj:`Filters `): + Pass one or more filters to allow only a subset of UserStatus updated to be passed in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func): + self.add_handler(pyrogram.UserStatusHandler(func, filters), group) + return func + + return decorator diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index 230d5e5d..74c97ca1 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -36,5 +36,5 @@ from .messages_and_media import ( from .update import Update from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, - Dialog, Dialogs, User + Dialog, Dialogs, User, UserStatus ) diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py index c8959708..748108de 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/client/types/update.py @@ -58,6 +58,9 @@ class Update(Object): pre_checkout_query (:obj:`PreCheckoutQuery `, *optional*): New incoming pre-checkout query. Contains full information about checkout. + + user_status (:obj:`UserStatus `, *optional*): + User status (last seen date) update. """ ID = 0xb0700000 @@ -74,7 +77,8 @@ class Update(Object): chosen_inline_result=None, callback_query=None, shipping_query=None, - pre_checkout_query=None + pre_checkout_query=None, + user_status=None ): self.message = message self.edited_message = edited_message @@ -87,3 +91,4 @@ class Update(Object): self.callback_query = callback_query self.shipping_query = shipping_query self.pre_checkout_query = pre_checkout_query + self.user_status = user_status diff --git a/pyrogram/client/types/user_and_chats/__init__.py b/pyrogram/client/types/user_and_chats/__init__.py index 45915edc..f4742d83 100644 --- a/pyrogram/client/types/user_and_chats/__init__.py +++ b/pyrogram/client/types/user_and_chats/__init__.py @@ -22,4 +22,5 @@ from .chat_members import ChatMembers from .chat_photo import ChatPhoto from .dialog import Dialog from .dialogs import Dialogs +from .user_status import UserStatus from .user import User diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 9ae5dab2..9c7eec1f 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -41,6 +41,9 @@ class User(Object): is_bot (``bool``): True, if this user is a bot. + status (:obj:`UserStatus `): + User's Last Seen status. Empty for bots. + first_name (``str``): User's or bot's first name. @@ -70,6 +73,7 @@ class User(Object): is_mutual_contact: bool, is_deleted: bool, is_bot: bool, + status, first_name: str, last_name: str = None, username: str = None, @@ -83,6 +87,7 @@ class User(Object): self.is_mutual_contact = is_mutual_contact self.is_deleted = is_deleted self.is_bot = is_bot + self.status = status self.first_name = first_name self.last_name = last_name self.username = username diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py new file mode 100644 index 00000000..17b73ea1 --- /dev/null +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -0,0 +1,84 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from pyrogram.api.core import Object + + +class UserStatus(Object): + """This object represents a User status (Last Seen privacy) + + .. note:: + + You won't see exact last seen timestamps for people with whom you don't share your own. Instead, you get + "recently", "within_week", "within_month" or "long_time_ago" fields set. + + Args: + user_id (``int``): + User's id. Only available for UserStatus updates. + + online (``bool``): + True if the user is online in this moment, None otherwise. + If True, the "date" field will be also set containing the online expiration date (i.e.: the date when a + user will automatically go offline in case of no action by his client). + + offline (``bool``): + True if the user is offline and has the Last Seen privacy setting visible for everybody, None otherwise. + If True, the "date" field will be also set containing the last seen date (i.e.: the date when a user + was online the last time). + + date (``int``): + Exact date in unix time. Available only in case "online" or "offline" equals to True. + + recently (``bool``): + True for users with hidden Last Seen privacy that have been online between 1 second and 2-3 days ago, + None otherwise. + + within_week (``bool``): + True for users with hidden Last Seen privacy that have been online between 2-3 and seven days ago, + None otherwise. + + within_month (``bool``): + True for users with hidden Last Seen privacy that have been online between 6-7 days and a month ago, + None otherwise. + + long_time_ago (``bool``): + True for users with hidden Last Seen privacy that have been online more than a month ago (this is also + always shown to blocked users), None otherwise. + """ + + ID = 0xb0700031 + + def __init__( + self, + user_id: int = None, + online: bool = None, + offline: bool = None, + date: int = None, + recently: bool = None, + within_week: bool = None, + within_month: bool = None, + long_time_ago: bool = None + ): + self.user_id = user_id + self.online = online + self.offline = offline + self.date = date + self.recently = recently + self.within_week = within_week + self.within_month = within_month + self.long_time_ago = long_time_ago