diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index dc0721577..f183fe33e 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see
-__version__ = "2.0.136"
+__version__ = "2.0.137"
__license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
__copyright__ = "Copyright (C) 2017-present Dan "
diff --git a/pyrogram/client.py b/pyrogram/client.py
index 298bdcbcb..f99808abd 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -44,12 +44,12 @@
from pyrogram.errors import (
SessionPasswordNeeded,
VolumeLocNotFound, ChannelPrivate,
- BadRequest
+ BadRequest, AuthBytesInvalid
)
from pyrogram.handlers.handler import Handler
from pyrogram.methods import Methods
from pyrogram.session import Auth, Session
-from pyrogram.storage import FileStorage, MemoryStorage
+from pyrogram.storage import Storage, FileStorage, MemoryStorage
from pyrogram.types import User, TermsOfService, ListenerStopped, ListenerTimeout, ListenerTypes
from pyrogram.utils import ainput, PyromodConfig
from .dispatcher import Dispatcher
@@ -177,6 +177,10 @@ class Client(Methods):
Set the maximum amount of concurrent transmissions (uploads & downloads).
A value that is too high may result in network related issues.
Defaults to 1.
+
+ storage_engine (:obj:`~pyrogram.storage.Storage`, *optional*):
+ Pass an instance of your own implementation of session storage engine.
+ Useful when you want to store your session in databases like Mongo, Redis, etc.
"""
APP_VERSION = f"Pyrogram {__version__}"
@@ -225,7 +229,8 @@ def __init__(
takeout: bool = None,
sleep_threshold: int = Session.SLEEP_THRESHOLD,
hide_password: bool = False,
- max_concurrent_transmissions: int = MAX_CONCURRENT_TRANSMISSIONS
+ max_concurrent_transmissions: int = MAX_CONCURRENT_TRANSMISSIONS,
+ storage_engine: Storage = None
):
super().__init__()
@@ -261,6 +266,8 @@ def __init__(
self.storage = MemoryStorage(self.name, self.session_string)
elif self.in_memory:
self.storage = MemoryStorage(self.name)
+ elif isinstance(storage_engine, Storage):
+ self.storage = storage_engine
else:
self.storage = FileStorage(self.name, self.workdir)
@@ -625,15 +632,15 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra
is_min = True
continue
- username = None
+ usernames = None
phone_number = None
if isinstance(peer, raw.types.User):
peer_id = peer.id
access_hash = peer.access_hash
- username = (
- peer.username.lower() if peer.username
- else peer.usernames[0].username.lower() if peer.usernames
+ usernames = (
+ [peer.username.lower()] if peer.username
+ else [username.username.lower() for username in peer.usernames] if peer.usernames
else None
)
phone_number = peer.phone
@@ -645,9 +652,9 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra
elif isinstance(peer, raw.types.Channel):
peer_id = utils.get_channel_id(peer.id)
access_hash = peer.access_hash
- username = (
- peer.username.lower() if peer.username
- else peer.usernames[0].username.lower() if peer.usernames
+ usernames = (
+ [peer.username.lower()] if peer.username
+ else [username.username.lower() for username in peer.usernames] if peer.usernames
else None
)
peer_type = "channel" if peer.broadcast else "supergroup"
@@ -658,7 +665,7 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra
else:
continue
- parsed_peers.append((peer_id, access_hash, peer_type, username, phone_number))
+ parsed_peers.append((peer_id, access_hash, peer_type, usernames, phone_number))
await self.storage.update_peers(parsed_peers)
@@ -946,7 +953,7 @@ async def get_file(
offset: int = 0,
progress: Callable = None,
progress_args: tuple = ()
- ) -> Optional[AsyncGenerator[bytes, None]]:
+ ) -> AsyncGenerator[bytes, None]:
async with self.get_file_semaphore:
file_type = file_id.file_type
@@ -994,31 +1001,40 @@ async def get_file(
dc_id = file_id.dc_id
- session = Session(
- self, dc_id,
- await Auth(self, dc_id, await self.storage.test_mode()).create()
- if dc_id != await self.storage.dc_id()
- else await self.storage.auth_key(),
- await self.storage.test_mode(),
- is_media=True
- )
-
try:
- await session.start()
-
- if dc_id != await self.storage.dc_id():
- exported_auth = await self.invoke(
- raw.functions.auth.ExportAuthorization(
- dc_id=dc_id
- )
+ session = self.media_sessions.get(dc_id)
+ if not session:
+ session = self.media_sessions[dc_id] = Session(
+ self, dc_id,
+ await Auth(self, dc_id, await self.storage.test_mode()).create()
+ if dc_id != await self.storage.dc_id()
+ else await self.storage.auth_key(),
+ await self.storage.test_mode(),
+ is_media=True
)
+ await session.start()
- await session.invoke(
- raw.functions.auth.ImportAuthorization(
- id=exported_auth.id,
- bytes=exported_auth.bytes
- )
- )
+ if dc_id != await self.storage.dc_id():
+ for _ in range(3):
+ exported_auth = await self.invoke(
+ raw.functions.auth.ExportAuthorization(
+ dc_id=dc_id
+ )
+ )
+
+ try:
+ await session.invoke(
+ raw.functions.auth.ImportAuthorization(
+ id=exported_auth.id,
+ bytes=exported_auth.bytes
+ )
+ )
+ except AuthBytesInvalid:
+ continue
+ else:
+ break
+ else:
+ raise AuthBytesInvalid
r = await session.invoke(
raw.functions.upload.GetFile(
@@ -1152,7 +1168,11 @@ async def get_file(
except Exception as e:
log.exception(e)
finally:
- await session.stop()
+ if session:
+ try:
+ await session.stop()
+ self.media_sessions.pop(session.dc_id)
+ except: pass
def guess_mime_type(self, filename: str) -> Optional[str]:
return self.mimetypes.guess_type(filename)[0]
diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py
index 096d6989a..a0ded834e 100644
--- a/pyrogram/dispatcher.py
+++ b/pyrogram/dispatcher.py
@@ -70,8 +70,13 @@ def __init__(self, client: "pyrogram.Client"):
async def message_parser(update, users, chats):
return (
- await pyrogram.types.Message._parse(self.client, update.message, users, chats, None,
- isinstance(update, UpdateNewScheduledMessage)),
+ await pyrogram.types.Message._parse(
+ self.client,
+ update.message,
+ users,
+ chats,
+ is_scheduled=isinstance(update, UpdateNewScheduledMessage)
+ ),
MessageHandler
)
diff --git a/pyrogram/enums/__init__.py b/pyrogram/enums/__init__.py
index f1f01b567..b0609761e 100644
--- a/pyrogram/enums/__init__.py
+++ b/pyrogram/enums/__init__.py
@@ -28,6 +28,8 @@
from .next_code_type import NextCodeType
from .parse_mode import ParseMode
from .poll_type import PollType
+from .profile_color import ProfileColor
+from .reply_color import ReplyColor
from .sent_code_type import SentCodeType
from .stories_privacy_rules import StoriesPrivacyRules
from .user_status import UserStatus
@@ -45,7 +47,9 @@
'NextCodeType',
'ParseMode',
'PollType',
+ 'ProfileColor',
+ 'ReplyColor',
'SentCodeType',
'StoriesPrivacyRules',
'UserStatus'
-]
\ No newline at end of file
+]
diff --git a/pyrogram/enums/chat_event_action.py b/pyrogram/enums/chat_event_action.py
index b46e01cfd..eff86f378 100644
--- a/pyrogram/enums/chat_event_action.py
+++ b/pyrogram/enums/chat_event_action.py
@@ -133,4 +133,4 @@ class ChatEventAction(AutoName):
"a forum topic has been deleted (see `deleted_forum_topic`)"
UNKNOWN = auto()
- "Unknown chat event action"
\ No newline at end of file
+ "Unknown chat event action"
diff --git a/pyrogram/enums/message_media_type.py b/pyrogram/enums/message_media_type.py
index 9da88a655..c68447162 100644
--- a/pyrogram/enums/message_media_type.py
+++ b/pyrogram/enums/message_media_type.py
@@ -69,5 +69,11 @@ class MessageMediaType(AutoName):
GAME = auto()
"Game media"
+ GIVEAWAY = auto()
+ "Giveaway media"
+
+ GIVEAWAY_RESULT = auto()
+ "Giveaway result media"
+
STORY = auto()
- "Story media"
\ No newline at end of file
+ "Story media"
diff --git a/pyrogram/enums/message_service_type.py b/pyrogram/enums/message_service_type.py
index 48e1fd8b0..6914e79a4 100644
--- a/pyrogram/enums/message_service_type.py
+++ b/pyrogram/enums/message_service_type.py
@@ -24,6 +24,9 @@
class MessageServiceType(AutoName):
"""Message service type enumeration used in :obj:`~pyrogram.types.Message`."""
+ CUSTOM_ACTION = auto()
+ "Custom action"
+
NEW_CHAT_MEMBERS = auto()
"New members join"
@@ -75,6 +78,12 @@ class MessageServiceType(AutoName):
GAME_HIGH_SCORE = auto()
"Game high score"
+ GIVEAWAY_LAUNCH = auto()
+ "Giveaway launch"
+
+ GIFT_CODE = auto()
+ "Gift code"
+
VIDEO_CHAT_STARTED = auto()
"Video chat started"
@@ -89,3 +98,6 @@ class MessageServiceType(AutoName):
WEB_APP_DATA = auto()
"Web app data"
+
+ REQUESTED_CHAT = auto()
+ "Requested chat"
diff --git a/pyrogram/enums/profile_color.py b/pyrogram/enums/profile_color.py
new file mode 100644
index 000000000..8ae81664d
--- /dev/null
+++ b/pyrogram/enums/profile_color.py
@@ -0,0 +1,71 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 .auto_name import AutoName
+
+
+class ProfileColor(AutoName):
+ """Profile color enumeration used in :meth:`~pyrogram.Client.update_color` and :obj:`~pyrogram.types.ChatColor`."""
+
+ RED = 0
+ "Red color."
+
+ ORANGE = 1
+ "Orange color."
+
+ VIOLET = 2
+ "Violet color."
+
+ GREEN = 3
+ "Green color."
+
+ CYAN = 4
+ "Cyan color."
+
+ BLUE = 5
+ "Blue color."
+
+ PINK = 6
+ "Pink color."
+
+ GRAY = 7
+ "Gray color."
+
+ RED_LIGHT_RED = 8
+ "Red color with light red gradient."
+
+ ORANGE_LIGHT_ORANGE = 9
+ "Orange color with light red gradient."
+
+ VIOLET_LIGHT_VIOLET = 10
+ "Violet color with light violet gradient."
+
+ GREEN_LIGHT_GREEN = 11
+ "Green color with light green gradient."
+
+ CYAN_LIGHT_CYAN = 12
+ "Cyan color with light cyan gradient."
+
+ BLUE_LIGHT_BLUE = 13
+ "Blue color with light blue gradient."
+
+ PINK_LIGHT_PINK = 14
+ "Pink color with light pink gradient."
+
+ GRAY_LIGHT_GRAY = 15
+ "Gray color with light gray gradient."
diff --git a/pyrogram/enums/reply_color.py b/pyrogram/enums/reply_color.py
new file mode 100644
index 000000000..c5bd25711
--- /dev/null
+++ b/pyrogram/enums/reply_color.py
@@ -0,0 +1,94 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 .auto_name import AutoName
+
+
+class ReplyColor(AutoName):
+ """Reply color enumeration used in :meth:`~pyrogram.Client.update_color` and :obj:`~pyrogram.types.ChatColor`."""
+
+ RED = 0
+ "Red color."
+
+ ORANGE = 1
+ "Orange color."
+
+ VIOLET = 2
+ "Violet color."
+
+ GREEN = 3
+ "Green color."
+
+ CYAN = 4
+ "Cyan color."
+
+ BLUE = 5
+ "Blue color."
+
+ PINK = 6
+ "Pink color."
+
+ RED_DARK_RED = 7
+ "Red color with dark red stripes."
+
+ ORANGE_DARK_ORANGE = 8
+ "Orange color with dark orange stripes."
+
+ VIOLET_DARK_VIOLET = 9
+ "Violet color with dark violet stripes."
+
+ GREEN_DARK_GREEN = 10
+ "Green color with dark green stripes."
+
+ CYAN_DARK_CYAN = 11
+ "Cyan color with dark cyan stripes."
+
+ BLUE_DARK_BLUE = 12
+ "Blue color with dark blue stripes."
+
+ PINK_DARK_PINK = 13
+ "Pink color with dark pink stripes."
+
+ BLUE_WHITE_RED = 14
+ "Blue color with white and red stripes."
+
+ ORANGE_WHITE_GREEN = 15
+ "Orange color with white and green stripes."
+
+ GREEN_WHITE_RED = 16
+ "Green color with white and red stripes."
+
+ CYAN_WHITE_GREEN = 17
+ "Cyan color with white and red green."
+
+ CYAN_YELLOW_PINK = 18
+ "Cyan color with yellow and pink stripes."
+
+ VIOLET_YELLOW_ORANGE = 19
+ "Violet color with yellow and orange stripes."
+
+ BLUE_WHITE_ORANGE = 20
+ "Blue color with white and orange stripes."
+
+ DYNAMIC = 21
+ """Secret color that cannot be set.
+
+ For now:
+ Red - If you use Telegram desktop.
+ Blue - If you are using Telegram android/ios.
+ """
diff --git a/pyrogram/enums/stories_privacy_rules.py b/pyrogram/enums/stories_privacy_rules.py
index c352f2b14..9696f0968 100644
--- a/pyrogram/enums/stories_privacy_rules.py
+++ b/pyrogram/enums/stories_privacy_rules.py
@@ -21,7 +21,7 @@
class StoriesPrivacyRules(AutoName):
- """Stories privacy rules type enumeration used in :obj:`~pyrogram.method.SendStory`."""
+ """Stories privacy rules type enumeration used in :meth:`~pyrogram.Client.send_story`."""
PUBLIC = auto()
"Public stories"
diff --git a/pyrogram/filters.py b/pyrogram/filters.py
index 6b867d4ed..9d566b2c1 100644
--- a/pyrogram/filters.py
+++ b/pyrogram/filters.py
@@ -182,6 +182,17 @@ async def bot_filter(_, __, m: Message):
"""Filter messages coming from bots."""
+# endregion
+
+# region sender_chat_filter
+async def sender_chat_filter(_, __, m: Message):
+ return bool(m.sender_chat)
+
+
+sender_chat = create(sender_chat_filter)
+"""Filter messages coming from sender chat."""
+
+
# endregion
# region incoming_filter
@@ -315,6 +326,50 @@ async def game_filter(_, __, m: Message):
"""Filter messages that contain :obj:`~pyrogram.types.Game` objects."""
+# endregion
+
+# region giveaway_filter
+async def giveaway_filter(_, __, m: Message):
+ return bool(m.giveaway)
+
+
+giveaway = create(giveaway_filter)
+"""Filter messages that contain :obj:`~pyrogram.types.Giveaway` objects."""
+
+
+# endregion
+
+# region giveaway_result_filter
+async def giveaway_result_filter(_, __, m: Message):
+ return bool(m.giveaway_result)
+
+
+giveaway_result = create(giveaway_result_filter)
+"""Filter messages that contain :obj:`~pyrogram.types.GiveawayResult` objects."""
+
+
+# endregion
+
+# region gift_code_filter
+async def gift_code_filter(_, __, m: Message):
+ return bool(m.gift_code)
+
+
+gift_code = create(gift_code_filter)
+"""Filter messages that contain :obj:`~pyrogram.types.GiftCode` objects."""
+
+
+# endregion
+
+# region requested_chats_filter
+async def requested_chats_filter(_, __, m: Message):
+ return bool(m.requested_chats)
+
+
+requested_chats = create(requested_chats_filter)
+"""Filter service messages for request chats."""
+
+
# endregion
# region video_filter
@@ -424,6 +479,7 @@ async def dice_filter(_, __, m: Message):
dice = create(dice_filter)
"""Filter messages that contain :obj:`~pyrogram.types.Dice` objects."""
+
# endregion
# region quote_filter
@@ -434,6 +490,7 @@ async def quote_filter(_, __, m: Message):
quote = create(quote_filter)
"""Filter quote messages."""
+
# endregion
# region media_spoiler
@@ -481,6 +538,28 @@ async def channel_filter(_, __, u: Update):
"""Filter messages sent in channels."""
+# endregion
+
+# region forum_filter
+async def forum_filter(_, __, m: Message):
+ return bool(m.chat and m.chat.is_forum)
+
+
+forum = create(forum_filter)
+"""Filter messages sent in forums."""
+
+
+# endregion
+
+# region story_filter
+async def story_filter(_, __, m: Message):
+ return bool(m.story)
+
+
+story = create(story_filter)
+"""Filter messages that contain :obj:`~pyrogram.types.Story` objects."""
+
+
# endregion
# region new_chat_members_filter
@@ -657,6 +736,17 @@ async def via_bot_filter(_, __, m: Message):
"""Filter messages sent via inline bots"""
+# endregion
+
+# region admin_filter
+async def admin_filter(_, __, m: Message):
+ return bool(m.chat and m.chat.is_admin)
+
+
+admin = create(admin_filter)
+"""Filter chats where you have admin rights"""
+
+
# endregion
# region video_chat_started_filter
diff --git a/pyrogram/methods/__init__.py b/pyrogram/methods/__init__.py
index a2cf696f5..f3d164b5e 100644
--- a/pyrogram/methods/__init__.py
+++ b/pyrogram/methods/__init__.py
@@ -25,8 +25,9 @@
from .invite_links import InviteLinks
from .messages import Messages
from .password import Password
-from .stories import Stories
+from .premium import Premium
from .users import Users
+from .stories import Stories
from .utilities import Utilities
@@ -36,9 +37,10 @@ class Methods(
Bots,
Contacts,
Password,
- Stories,
+ Premium,
Chats,
Users,
+ Stories,
Messages,
Decorators,
Utilities,
diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py
index 453a62af1..54ff537a6 100644
--- a/pyrogram/methods/advanced/save_file.py
+++ b/pyrogram/methods/advanced/save_file.py
@@ -19,6 +19,7 @@
import asyncio
import functools
import inspect
+import datetime
import io
import logging
import math
@@ -139,16 +140,23 @@ async def worker(session):
is_missing_part = file_id is not None
file_id = file_id or self.rnd_id()
md5_sum = md5() if not is_big and not is_missing_part else None
- session = Session(
- self, await self.storage.dc_id(), await self.storage.auth_key(),
- await self.storage.test_mode(), is_media=True
- )
+ dc_id = await self.storage.dc_id()
+
+ async with self.media_sessions_lock:
+ session = self.media_sessions.get(dc_id)
+ if not session:
+ session = Session(
+ self, dc_id, await self.storage.auth_key(),
+ await self.storage.test_mode(), is_media=True
+ )
+ await session.start()
+
+ session.last_used_time = datetime.datetime.now()
+
workers = [self.loop.create_task(worker(session)) for _ in range(workers_count)]
queue = asyncio.Queue(1)
try:
- await session.start()
-
fp.seek(part_size * file_part)
while True:
@@ -220,7 +228,30 @@ async def worker(session):
await asyncio.gather(*workers)
- await session.stop()
-
if isinstance(path, (str, PurePath)):
fp.close()
+
+ async def close_unused_sessions(
+ self: "pyrogram.Client",
+ minutes_passed: int = 20,
+ ) -> int:
+ """Closes all the sessions that haven't been used in last X amount of time.
+ """
+ if not minutes_passed:
+ return None
+
+ now = datetime.datetime.now()
+ async with self.media_sessions_lock:
+ all_keys = self.media_sessions.keys()
+ for current in all_keys:
+ last_time_used = getattr(self.media_sessions[current], "last_time_used", None)
+
+ if not isinstance(last_time_used, datetime.datetime):
+ continue
+
+ if (now - last_time_used).seconds > 60*minutes_passed:
+ current_media = self.media_sessions.pop(current)
+ if not isinstance(current_media, Session):
+ continue
+
+ await current_media.stop()
diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py
index f1aa7e91c..4027e0df5 100644
--- a/pyrogram/methods/bots/send_game.py
+++ b/pyrogram/methods/bots/send_game.py
@@ -32,7 +32,7 @@ async def send_game(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
protect_content: bool = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
@@ -60,11 +60,14 @@ async def send_game(
message_thread_id (``int``, *optional*):
Unique identifier of a message thread to which the message belongs.
- for supergroups only
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
protect_content (``bool``, *optional*):
Protects the contents of the sent message from forwarding and saving.
diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py
index 23cca6f2a..4b7e0dbf6 100644
--- a/pyrogram/methods/bots/send_inline_bot_result.py
+++ b/pyrogram/methods/bots/send_inline_bot_result.py
@@ -32,7 +32,7 @@ async def send_inline_bot_result(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
quote_entities: List["types.MessageEntity"] = None,
diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py
index cb287d767..570d8bdf6 100644
--- a/pyrogram/methods/chats/__init__.py
+++ b/pyrogram/methods/chats/__init__.py
@@ -26,24 +26,30 @@
from .close_forum_topic import CloseForumTopic
from .delete_channel import DeleteChannel
from .delete_chat_photo import DeleteChatPhoto
+from .delete_folder import DeleteFolder
from .delete_forum_topic import DeleteForumTopic
from .delete_supergroup import DeleteSupergroup
from .delete_user_history import DeleteUserHistory
from .edit_forum_topic import EditForumTopic
+from .export_folder_link import ExportFolderLink
from .get_chat import GetChat
from .get_chat_event_log import GetChatEventLog
from .get_chat_member import GetChatMember
from .get_chat_members import GetChatMembers
from .get_chat_members_count import GetChatMembersCount
from .get_chat_online_count import GetChatOnlineCount
+from .get_similar_channels import GetSimilarChannels
from .get_dialogs import GetDialogs
from .get_dialogs_count import GetDialogsCount
+from .get_folders import GetFolders
from .get_forum_topics import GetForumTopics
from .get_forum_topics_by_id import GetForumTopicsByID
from .get_nearby_chats import GetNearbyChats
from .get_send_as_chats import GetSendAsChats
from .join_chat import JoinChat
+from .join_folder import JoinFolder
from .leave_chat import LeaveChat
+from .leave_folder import LeaveFolder
from .mark_chat_unread import MarkChatUnread
from .pin_chat_message import PinChatMessage
from .promote_chat_member import PromoteChatMember
@@ -54,20 +60,26 @@
from .set_chat_photo import SetChatPhoto
from .set_chat_protected_content import SetChatProtectedContent
from .set_chat_title import SetChatTitle
+from .set_chat_ttl import SetChatTTL
from .set_chat_username import SetChatUsername
from .set_send_as_chat import SetSendAsChat
from .set_slow_mode import SetSlowMode
+from .toggle_forum_topics import ToggleForumTopics
from .unarchive_chats import UnarchiveChats
from .unban_chat_member import UnbanChatMember
from .unpin_all_chat_messages import UnpinAllChatMessages
from .unpin_chat_message import UnpinChatMessage
+from .update_chat_notifications import UpdateChatNotifications
from .update_color import UpdateColor
+from .update_folder import UpdateFolder
class Chats(
GetChat,
LeaveChat,
+ LeaveFolder,
JoinChat,
+ JoinFolder,
BanChatMember,
UnbanChatMember,
RestrictChatMember,
@@ -76,16 +88,21 @@ class Chats(
GetChatMember,
SetChatPhoto,
DeleteChatPhoto,
+ DeleteFolder,
SetChatTitle,
+ SetChatTTL,
SetChatDescription,
PinChatMessage,
UnpinChatMessage,
+ UpdateChatNotifications,
UpdateColor,
+ UpdateFolder,
GetDialogs,
GetChatMembersCount,
SetChatUsername,
SetChatPermissions,
GetDialogsCount,
+ GetFolders,
GetForumTopics,
GetForumTopicsByID,
ArchiveChats,
@@ -100,14 +117,17 @@ class Chats(
DeleteForumTopic,
DeleteSupergroup,
EditForumTopic,
+ ExportFolderLink,
GetNearbyChats,
SetAdministratorTitle,
SetSlowMode,
+ ToggleForumTopics,
DeleteUserHistory,
UnpinAllChatMessages,
MarkChatUnread,
GetChatEventLog,
GetChatOnlineCount,
+ GetSimilarChannels,
GetSendAsChats,
SetSendAsChat,
SetChatProtectedContent
diff --git a/pyrogram/methods/chats/delete_folder.py b/pyrogram/methods/chats/delete_folder.py
new file mode 100644
index 000000000..fada0ffb5
--- /dev/null
+++ b/pyrogram/methods/chats/delete_folder.py
@@ -0,0 +1,51 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 pyrogram import raw
+
+
+class DeleteFolder:
+ async def delete_folder(
+ self: "pyrogram.Client",
+ folder_id: int
+ ) -> bool:
+ """Delete a user's folder.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ folder_id (``int``):
+ Unique identifier (int) of the target folder.
+
+ Returns:
+ ``bool``: True, on success.
+
+ Example:
+ .. code-block:: python
+
+ # Delete folder
+ app.delete_folder(folder_id)
+ """
+ r = await self.invoke(
+ raw.functions.messages.UpdateDialogFilter(
+ id=folder_id
+ )
+ )
+
+ return r
\ No newline at end of file
diff --git a/pyrogram/methods/chats/export_folder_link.py b/pyrogram/methods/chats/export_folder_link.py
new file mode 100644
index 000000000..7072b81f3
--- /dev/null
+++ b/pyrogram/methods/chats/export_folder_link.py
@@ -0,0 +1,69 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 pyrogram import raw
+
+
+class ExportFolderLink:
+ async def export_folder_link(
+ self: "pyrogram.Client",
+ folder_id: int
+ ) -> str:
+ """Export link to a user's folder.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ folder_id (``int``):
+ Unique identifier (int) of the target folder.
+
+ Returns:
+ ``str``: On success, a link to the folder as string is returned.
+
+ Example:
+ .. code-block:: python
+
+ # Export folder link
+ app.export_folder_link(folder_id)
+ """
+ folder = await self.get_folders(folder_id)
+
+ if not folder:
+ return
+
+ peers = []
+
+ if folder.included_chats:
+ peers.extend(iter(folder.included_chats))
+
+ if folder.excluded_chats:
+ peers.extend(iter(folder.included_chats))
+
+ if folder.pinned_chats:
+ peers.extend(iter(folder.included_chats))
+
+ r = await self.invoke(
+ raw.functions.chatlists.ExportChatlistInvite(
+ chatlist=raw.types.InputChatlistDialogFilter(filter_id=folder_id),
+ title=folder.title,
+ peers=[await self.resolve_peer(i.id) for i in peers],
+ )
+ )
+
+ return r.invite.url
\ No newline at end of file
diff --git a/pyrogram/methods/chats/get_folders.py b/pyrogram/methods/chats/get_folders.py
new file mode 100644
index 000000000..66b0ad721
--- /dev/null
+++ b/pyrogram/methods/chats/get_folders.py
@@ -0,0 +1,90 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import Union, List, Iterable
+
+import pyrogram
+from pyrogram import types, raw
+
+
+class GetFolders:
+ async def get_folders(
+ self: "pyrogram.Client",
+ folder_ids: Union[int, Iterable[int]] = None,
+ ) -> Union["types.Folder", List["types.Folder"]]:
+ """Get one or more folders by using folder identifiers.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ folder_ids (``int`` | Iterable of ``int``, *optional*):
+ Pass a single folder identifier or an iterable of folder ids (as integers) to get the content of the
+ folders themselves.
+ By default all folders are returned.
+
+ Returns:
+ :obj:`~pyrogram.types.Folder` | List of :obj:`~pyrogram.types.Folder`: In case *folder_ids* was not
+ a list, a single folder is returned, otherwise a list of folders is returned.
+
+ Example:
+ .. code-block:: python
+
+ # Get one folder
+ await app.get_folders(12345)
+
+ # Get more than one folders (list of folders)
+ await app.get_folders([12345, 12346])
+
+ # Get all folders
+ await app.get_folders()
+ """
+ is_iterable = hasattr(folder_ids, "__iter__")
+ ids = list(folder_ids) if is_iterable else [folder_ids]
+
+ raw_folders = await self.invoke(raw.functions.messages.GetDialogFilters())
+ dialog_peers = []
+
+ for folder in raw_folders:
+ if isinstance(folder, (raw.types.DialogFilter, raw.types.DialogFilterChatlist)):
+ peers = folder.pinned_peers + folder.include_peers + getattr(folder, "exclude_peers", [])
+ input_peers = [raw.types.InputDialogPeer(peer=peer) for peer in peers] + [raw.types.InputDialogPeerFolder(folder_id=folder.id)]
+
+ dialog_peers.extend(input_peers)
+
+ r = await self.invoke(raw.functions.messages.GetPeerDialogs(peers=dialog_peers))
+
+ users = {i.id: i for i in r.users}
+ chats = {i.id: i for i in r.chats}
+
+ folders = types.List([])
+
+ for folder in raw_folders:
+ if isinstance(folder, (raw.types.DialogFilter, raw.types.DialogFilterChatlist)):
+ folders.append(types.Folder._parse(self, folder, users, chats))
+
+ if not folders:
+ return None
+
+ if folder_ids:
+ folders = types.List([folder for folder in folders if folder.id in ids])
+ if is_iterable:
+ return folders or None
+ else:
+ return folders[0] if folders else None
+
+ return folders
\ No newline at end of file
diff --git a/pyrogram/methods/chats/get_similar_channels.py b/pyrogram/methods/chats/get_similar_channels.py
new file mode 100644
index 000000000..356361c77
--- /dev/null
+++ b/pyrogram/methods/chats/get_similar_channels.py
@@ -0,0 +1,59 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import Union, List, Optional
+
+import pyrogram
+from pyrogram import raw
+from pyrogram import types
+
+
+class GetSimilarChannels:
+ async def get_similar_channels(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str]
+ ) -> Optional[List["types.Chat"]]:
+ """Get similar channels.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+
+ Returns:
+ List of :obj:`~pyrogram.types.Chat`: On success, the list of channels is returned.
+
+ Example:
+ .. code-block:: python
+
+ channels = await app.get_similar_channels(chat_id)
+ print(channels)
+ """
+ chat = await self.resolve_peer(chat_id)
+
+ if isinstance(chat, raw.types.InputPeerChannel):
+ r = await self.invoke(
+ raw.functions.channels.GetChannelRecommendations(
+ channel=chat
+ )
+ )
+
+ return types.List([types.Chat._parse_channel_chat(self, chat) for chat in r.chats]) or None
+ else:
+ raise ValueError(f'The chat_id "{chat_id}" belongs to a user or chat')
\ No newline at end of file
diff --git a/pyrogram/methods/chats/join_folder.py b/pyrogram/methods/chats/join_folder.py
new file mode 100644
index 000000000..e004dcfea
--- /dev/null
+++ b/pyrogram/methods/chats/join_folder.py
@@ -0,0 +1,75 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 re
+
+import pyrogram
+from pyrogram import raw, utils
+
+
+class JoinFolder:
+ async def join_folder(
+ self: "pyrogram.Client",
+ link: str,
+ ) -> bool:
+ """Join a folder by its invite link.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ link (``str``):
+ Invite link of the folder.
+
+ Returns:
+ ``bool``: True, on success.
+
+ Raises:
+ BadRequest: In case the folder invite link not exists.
+ ValueError: In case the folder invite link is invalid.
+
+ Example:
+ .. code-block:: python
+
+ # join folder
+ app.join_folder("t.me/addlist/ebXQ0Q0I3RnGQ")
+ """
+ match = re.match(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:addlist/|\+))([\w-]+)$", link)
+
+ if not match:
+ raise ValueError("Invalid folder invite link")
+
+ r = await self.invoke(
+ raw.functions.chatlists.CheckChatlistInvite(
+ slug=match.group(1)
+ )
+ )
+
+ if isinstance(r, raw.types.chatlists.ChatlistInviteAlready):
+ peers = r.already_peers + r.missing_peers
+ else:
+ peers = r.peers
+
+ await self.invoke(
+ raw.functions.chatlists.JoinChatlistInvite(
+ slug=match.group(1),
+ peers=[
+ await self.resolve_peer(utils.get_peer_id(id)) for id in peers
+ ],
+ )
+ )
+
+ return True
\ No newline at end of file
diff --git a/pyrogram/methods/chats/leave_folder.py b/pyrogram/methods/chats/leave_folder.py
new file mode 100644
index 000000000..70d93824b
--- /dev/null
+++ b/pyrogram/methods/chats/leave_folder.py
@@ -0,0 +1,79 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 re
+
+import pyrogram
+from pyrogram import raw, utils
+
+
+class LeaveFolder:
+ async def leave_folder(
+ self: "pyrogram.Client",
+ link: str,
+ keep_chats: bool = True
+ ) -> bool:
+ """Leave a folder by its invite link.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ link (``str``):
+ Invite link of the folder.
+
+ keep_chats (``bool``, *optional*):
+ If True, the chats from the folder will be kept in the user's account.
+ Defaults to True.
+
+ Returns:
+ ``bool``: True, on success.
+
+ Raises:
+ AttributeError: In case the folder invite link does not exist in the user's account.
+ BadRequest: In case the folder invite link not exists.
+ ValueError: In case the folder invite link is invalid.
+
+ Example:
+ .. code-block:: python
+
+ # leave folder
+ app.leave_folder("t.me/addlist/ebXQ0Q0I3RnGQ")
+ """
+ match = re.match(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:addlist/|\+))([\w-]+)$", link)
+
+ if not match:
+ raise ValueError("Invalid folder invite link")
+
+ r = await self.invoke(
+ raw.functions.chatlists.CheckChatlistInvite(
+ slug=match.group(1)
+ )
+ )
+
+ await self.invoke(
+ raw.functions.chatlists.LeaveChatlist(
+ chatlist=raw.types.InputChatlistDialogFilter(
+ filter_id=r.filter_id
+ ),
+ peers=[
+ await self.resolve_peer(utils.get_peer_id(id))
+ for id in r.already_peers
+ ] if not keep_chats else [],
+ )
+ )
+
+ return True
\ No newline at end of file
diff --git a/pyrogram/methods/chats/set_chat_ttl.py b/pyrogram/methods/chats/set_chat_ttl.py
new file mode 100644
index 000000000..ef97d3976
--- /dev/null
+++ b/pyrogram/methods/chats/set_chat_ttl.py
@@ -0,0 +1,64 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2021 Dan
+#
+# 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 typing import Union
+
+import pyrogram
+from pyrogram import raw
+from pyrogram import types
+
+
+class SetChatTTL:
+ async def set_chat_ttl(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str],
+ ttl_seconds: int
+ ) -> "types.Message":
+ """Set the time-to-live for the chat.
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+
+ ttl_seconds (``int``):
+ The time-to-live for the chat.
+ Either 86000 for 1 day, 604800 for 1 week or 0 (zero) to disable it.
+
+ Returns:
+ ``bool``: True on success.
+
+ Example:
+ .. code-block:: python
+
+ # Set TTL for a chat to 1 day
+ app.set_chat_ttl(chat_id, 86400)
+
+ # Set TTL for a chat to 1 week
+ app.set_chat_ttl(chat_id, 604800)
+
+ # Disable TTL for this chat
+ app.set_chat_ttl(chat_id, 0)
+ """
+ await self.invoke(
+ raw.functions.messages.SetHistoryTTL(
+ peer=await self.resolve_peer(chat_id),
+ period=ttl_seconds,
+ )
+ )
+
+ return True
\ No newline at end of file
diff --git a/pyrogram/methods/chats/toggle_forum_topics.py b/pyrogram/methods/chats/toggle_forum_topics.py
new file mode 100644
index 000000000..2576e0f18
--- /dev/null
+++ b/pyrogram/methods/chats/toggle_forum_topics.py
@@ -0,0 +1,65 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import Union
+
+import pyrogram
+from pyrogram import raw
+from pyrogram import errors
+
+
+class ToggleForumTopics:
+ async def toggle_forum_topics(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str],
+ enabled: bool = False
+ ) -> bool:
+ """Enable or disable forum functionality in a supergroup.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+
+ enabled (``bool``):
+ The new status. Pass True to enable forum topics.
+
+ Returns:
+ ``bool``: True on success. False otherwise.
+
+ Example:
+ .. code-block:: python
+
+ # Change status of topics to disabled
+ await app.toggle_topics()
+
+ # Change status of topics to enabled
+ await app.toggle_topics(enabled=True)
+ """
+ try:
+ r = await self.invoke(
+ raw.functions.channels.ToggleForum(
+ channel=await self.resolve_peer(chat_id),
+ enabled=enabled
+ )
+ )
+
+ return bool(r)
+ except errors.RPCError:
+ return False
\ No newline at end of file
diff --git a/pyrogram/methods/chats/update_chat_notifications.py b/pyrogram/methods/chats/update_chat_notifications.py
new file mode 100644
index 000000000..1c3657892
--- /dev/null
+++ b/pyrogram/methods/chats/update_chat_notifications.py
@@ -0,0 +1,93 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 datetime import datetime, timedelta
+from typing import Union
+
+import pyrogram
+from pyrogram import raw
+from pyrogram import types
+from pyrogram import utils
+
+class UpdateChatNotifications:
+ async def update_chat_notifications(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str],
+ mute: bool = None,
+ mute_until: datetime = None,
+ stories_muted: bool = None,
+ stories_hide_sender: bool = None,
+ show_previews: bool = None
+ ) -> "types.Chat":
+ """Update the notification settings for the selected chat
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+
+ mute (``bool``, *optional*):
+ Pass True if you want to mute chat.
+
+ until_date (:py:obj:`~datetime.datetime`, *optional*):
+ Date when the user will be unmuted. Works only if the mute parameter is set to True. Defaults to forever.
+
+ stories_muted (``bool``, *optional*):
+ N/A
+
+ stories_hide_sender (``bool``, *optional*):
+ N/A
+
+ show_previews (``bool``, *optional*):
+ If the text of the message shall be displayed in notification.
+
+ Returns:
+ ``bool``: True on success, False otherwise.
+
+ Example:
+ .. code-block:: python
+
+ # Mute a chat permanently
+ app.update_chat_notifications(chat_id, mute=True)
+
+ # Mute a chat for 10 minutes
+ app.update_chat_notifications(
+ chat_id,
+ mute=True
+ mute_until=datetime.timedelta(minutes=10)
+ )
+
+ # Unmute a chat
+ app.update_chat_notifications(chat_id, mute=False)
+ """
+ if not mute_until:
+ mute_until = utils.max_datetime() if mute else utils.zero_datetime()
+
+ r = await self.invoke(
+ raw.functions.account.UpdateNotifySettings(
+ peer=raw.types.InputNotifyPeer(peer=await self.resolve_peer(chat_id)),
+ settings=raw.types.InputPeerNotifySettings(
+ show_previews=show_previews,
+ silent=mute,
+ mute_until=utils.datetime_to_timestamp(mute_until),
+ stories_muted=stories_muted,
+ stories_hide_sender=stories_hide_sender,
+ )
+ )
+ )
+
+ return r
\ No newline at end of file
diff --git a/pyrogram/methods/chats/update_color.py b/pyrogram/methods/chats/update_color.py
index 424e2cb0b..2543c3ff0 100644
--- a/pyrogram/methods/chats/update_color.py
+++ b/pyrogram/methods/chats/update_color.py
@@ -20,60 +20,57 @@
import pyrogram
from pyrogram import raw
-from pyrogram import types
-
-# account.updateColor flags: color:int background_emoji_id:flags.0?long = Bool;
-# channels.updateColor flags: channel:InputChannel color:int background_emoji_id:flags.0?long = Updates;
+from pyrogram import enums
class UpdateColor:
async def update_color(
self: "pyrogram.Client",
chat_id: Union[int, str],
- color: int,
- background_emoji_id: int = None,
- ) -> "types.Chat":
+ color: Union["enums.ReplyColor", "enums.ProfileColor"],
+ background_emoji_id: int = None
+ ) -> bool:
"""Update color
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
- color (``int``):
- Color
+ color (:obj:`~pyrogram.enums.ReplyColor` | :obj:`~pyrogram.enums.ProfileColor`):
+ Color type.
+ Pass :obj:`~pyrogram.enums.ReplyColor` to set reply color or
+ :obj:`~pyrogram.enums.ProfileColor` to set profile color.
background_emoji_id (``int``, *optional*):
- Background emoji
+ Unique identifier of the custom emoji.
Returns:
- ``bool``: True on success.
+ ``bool``: On success, in case the passed-in session is authorized, True is returned.
Example:
.. code-block:: python
- await app.update_color(chat_id, 1)
+ await app.update_color(chat_id, enums.ReplyColor.RED)
"""
-
peer = await self.resolve_peer(chat_id)
if isinstance(peer, raw.types.InputPeerSelf):
- await self.invoke(
+ r = await self.invoke(
raw.functions.account.UpdateColor(
- color=color,
+ for_profile=isinstance(color, enums.ProfileColor),
+ color=color.value,
background_emoji_id=background_emoji_id
)
)
-
- r = await self.invoke(raw.functions.users.GetUsers(id=[raw.types.InputPeerSelf()]))
- return types.Chat._parse_user_chat(self, r[0])
else:
r = await self.invoke(
raw.functions.channels.UpdateColor(
channel=peer,
- color=color,
+ for_profile=isinstance(color, enums.ProfileColor),
+ color=color.value,
background_emoji_id=background_emoji_id
)
)
- return types.Chat._parse_channel_chat(self, r.chats[0])
\ No newline at end of file
+ return bool(r)
\ No newline at end of file
diff --git a/pyrogram/methods/chats/update_folder.py b/pyrogram/methods/chats/update_folder.py
new file mode 100644
index 000000000..b38ace87a
--- /dev/null
+++ b/pyrogram/methods/chats/update_folder.py
@@ -0,0 +1,141 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import List, Union
+
+import pyrogram
+from pyrogram import raw
+
+
+class UpdateFolder:
+ async def update_folder(
+ self: "pyrogram.Client",
+ folder_id: int,
+ title: str,
+ included_chats: Union[Union[int, str], List[Union[int, str]]] = None,
+ excluded_chats: Union[Union[int, str], List[Union[int, str]]] = None,
+ pinned_chats: Union[Union[int, str], List[Union[int, str]]] = None,
+ contacts: bool = None,
+ non_contacts: bool = None,
+ groups: bool = None,
+ channels: bool = None,
+ bots: bool = None,
+ exclude_muted: bool = None,
+ exclude_read: bool = None,
+ exclude_archived: bool = None,
+ emoji: str = None
+ ) -> bool:
+ """Create or update a user's folder.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ folder_id (``int``):
+ Unique folder identifier.
+
+ title (``str``):
+ Folder title.
+
+ included_chats (``int`` | ``str`` | List of ``int`` or ``str``, *optional*):
+ Users or chats that should added in the folder
+ You can pass an ID (int), username (str) or phone number (str).
+ Multiple users can be added by passing a list of IDs, usernames or phone numbers.
+
+ excluded_chats (``int`` | ``str`` | List of ``int`` or ``str``, *optional*):
+ Users or chats that should excluded from the folder
+ You can pass an ID (int), username (str) or phone number (str).
+ Multiple users can be added by passing a list of IDs, usernames or phone numbers.
+
+ pinned_chats (``int`` | ``str`` | List of ``int`` or ``str``, *optional*):
+ Users or chats that should pinned in the folder
+ You can pass an ID (int), username (str) or phone number (str).
+ Multiple users can be added by passing a list of IDs, usernames or phone numbers.
+
+ contacts (``bool``, *optional*):
+ Pass True if folder should contain contacts.
+
+ non_contacts (``bool``, *optional*):
+ Pass True if folder should contain non contacts.
+
+ groups (``bool``, *optional*):
+ Pass True if folder should contain groups.
+
+ channels (``bool``, *optional*):
+ Pass True if folder should contain channels.
+
+ bots (``bool``, *optional*):
+ Pass True if folder should contain bots.
+
+ exclude_muted (``bool``, *optional*):
+ Pass True if folder should exclude muted users.
+
+ exclude_archived (``bool``, *optional*):
+ Pass True if folder should exclude archived users.
+
+ emoji (``str``, *optional*):
+ Folder emoji.
+ Pass None to leave the folder icon as default.
+
+ Returns:
+ ``bool``: True, on success.
+
+ Example:
+ .. code-block:: python
+
+ # Create or update folder
+ app.update_folder(folder_id, title="New folder", included_chats="me")
+ """
+ if not isinstance(included_chats, list):
+ included_chats = [included_chats] if included_chats else []
+ if not isinstance(excluded_chats, list):
+ excluded_chats = [excluded_chats] if excluded_chats else []
+ if not isinstance(pinned_chats, list):
+ pinned_chats = [pinned_chats] if pinned_chats else []
+
+ r = await self.invoke(
+ raw.functions.messages.UpdateDialogFilter(
+ id=folder_id,
+ filter=raw.types.DialogFilter(
+ id=folder_id,
+ title=title,
+ pinned_peers=[
+ await self.resolve_peer(user_id)
+ for user_id in pinned_chats
+ ],
+ include_peers=[
+ await self.resolve_peer(user_id)
+ for user_id in included_chats
+ ],
+ exclude_peers=[
+ await self.resolve_peer(user_id)
+ for user_id in excluded_chats
+ ],
+ contacts=contacts,
+ non_contacts=non_contacts,
+ groups=groups,
+ broadcasts=channels,
+ bots=bots,
+ exclude_muted=exclude_muted,
+ exclude_read=exclude_read,
+ exclude_archived=exclude_archived,
+ emoticon=emoji
+ )
+ )
+ )
+
+ return r
\ No newline at end of file
diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
index 62acca10e..e617098a5 100644
--- a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
+++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
@@ -30,13 +30,13 @@ async def get_chat_admin_invite_links(
admin_id: Union[int, str],
revoked: bool = False,
limit: int = 0,
- ) -> Optional[AsyncGenerator["types.ChatInviteLink", None]]:
+ ) -> AsyncGenerator["types.ChatInviteLink", None]:
"""Get the invite links created by an administrator in a chat.
.. note::
As an administrator you can only get your own links you have exported.
- As the chat or channel owner you can get everyones links.
+ As the chat or channel owner you can get everyone's links.
.. include:: /_includes/usable-by/users.rst
diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py b/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py
index c1fc43a71..46c105974 100644
--- a/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py
+++ b/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py
@@ -29,7 +29,7 @@ async def get_chat_invite_link_joiners(
chat_id: Union[int, str],
invite_link: str,
limit: int = 0
- ) -> Optional[AsyncGenerator["types.ChatJoiner", None]]:
+ ) -> AsyncGenerator["types.ChatJoiner", None]:
"""Get the members who joined the chat with the invite link.
.. include:: /_includes/usable-by/users.rst
diff --git a/pyrogram/methods/invite_links/get_chat_join_requests.py b/pyrogram/methods/invite_links/get_chat_join_requests.py
index a75498e2f..e0652e379 100644
--- a/pyrogram/methods/invite_links/get_chat_join_requests.py
+++ b/pyrogram/methods/invite_links/get_chat_join_requests.py
@@ -29,7 +29,7 @@ async def get_chat_join_requests(
chat_id: Union[int, str],
limit: int = 0,
query: str = ""
- ) -> Optional[AsyncGenerator["types.ChatJoiner", None]]:
+ ) -> AsyncGenerator["types.ChatJoiner", None]:
"""Get the pending join requests of a chat.
.. include:: /_includes/usable-by/users.rst
diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py
index bcd2028b9..ba699c0ed 100644
--- a/pyrogram/methods/messages/__init__.py
+++ b/pyrogram/methods/messages/__init__.py
@@ -38,6 +38,8 @@
from .get_media_group import GetMediaGroup
from .get_messages import GetMessages
from .read_chat_history import ReadChatHistory
+from .read_mentions import ReadMentions
+from .read_reactions import ReadReactions
from .retract_vote import RetractVote
from .search_global import SearchGlobal
from .search_global_count import SearchGlobalCount
@@ -61,11 +63,11 @@
from .send_video import SendVideo
from .send_video_note import SendVideoNote
from .send_voice import SendVoice
+from .send_web_page import SendWebPage
+from .start_bot import StartBot
from .stop_poll import StopPoll
from .stream_media import StreamMedia
from .vote_poll import VotePoll
-from .wait_for_message import WaitForMessage
-from .wait_for_callback_query import WaitForCallbackQuery
class Messages(
@@ -92,7 +94,9 @@ class Messages(
SendVideoNote,
SendVoice,
SendPoll,
+ SendWebPage,
VotePoll,
+ StartBot,
StopPoll,
RetractVote,
DownloadMedia,
@@ -100,6 +104,8 @@ class Messages(
SendCachedMedia,
GetChatHistoryCount,
ReadChatHistory,
+ ReadMentions,
+ ReadReactions,
EditInlineText,
EditInlineCaption,
EditInlineMedia,
@@ -116,8 +122,6 @@ class Messages(
GetDiscussionReplies,
GetDiscussionRepliesCount,
StreamMedia,
- GetCustomEmojiStickers,
- WaitForMessage,
- WaitForCallbackQuery
+ GetCustomEmojiStickers
):
pass
diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py
index b2f01516a..c4c3db010 100644
--- a/pyrogram/methods/messages/copy_media_group.py
+++ b/pyrogram/methods/messages/copy_media_group.py
@@ -17,10 +17,10 @@
# along with Pyrogram. If not, see .
from datetime import datetime
-from typing import Union, List
+from typing import Union, List, Optional
import pyrogram
-from pyrogram import types, utils, raw
+from pyrogram import types, utils, raw, enums
class CopyMediaGroup:
@@ -33,11 +33,14 @@ async def copy_media_group(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
+ parse_mode: Optional["enums.ParseMode"] = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
+ invert_media: bool = None,
) -> List["types.Message"]:
"""Copy a media group by providing one of the message ids.
@@ -70,15 +73,39 @@ async def copy_media_group(
Sends the message silently.
Users will receive a notification with no sound.
+ message_thread_id (``int``, *optional*):
+ Unique identifier for the target message thread (topic) of the forum.
+ For supergroups only.
+
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
- message_thread_id (``int``, *optional*):
- If the message is in a thread, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
+ reply_to_story_id (``int``, *optional*):
+ Unique identifier for the target story.
+
+ quote_text (``str``, *optional*):
+ Text of the quote to be sent.
+
+ parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+ By default, texts are parsed using both Markdown and HTML styles.
+ You can combine both syntaxes together.
+
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
+ List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
+ invert_media (``bool``, *optional*):
+ If True, link preview will be shown above the message text.
+ Otherwise, the link preview will be shown below the message text.
+
Returns:
List of :obj:`~pyrogram.types.Message`: On success, a list of copied messages is returned.
@@ -93,6 +120,7 @@ async def copy_media_group(
await app.copy_media_group(to_chat, from_chat, 123,
captions=["caption 1", None, ""])
"""
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
media_group = await self.get_media_group(from_chat_id, message_id)
multi_media = []
@@ -134,8 +162,10 @@ async def copy_media_group(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
- schedule_date=utils.datetime_to_timestamp(schedule_date)
+ schedule_date=utils.datetime_to_timestamp(schedule_date),
+ invert_media=invert_media
),
sleep_threshold=60
)
diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py
index 0b6624b28..fa45b41f8 100644
--- a/pyrogram/methods/messages/copy_message.py
+++ b/pyrogram/methods/messages/copy_message.py
@@ -21,7 +21,7 @@
from typing import Union, List, Optional
import pyrogram
-from pyrogram import types, enums
+from pyrogram import types, enums, utils
log = logging.getLogger(__name__)
@@ -36,9 +36,12 @@ async def copy_message(
parse_mode: Optional["enums.ParseMode"] = None,
caption_entities: List["types.MessageEntity"] = None,
disable_notification: bool = None,
+ message_thread_id: int = None,
reply_to_message_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
schedule_date: datetime = None,
protect_content: bool = None,
+ has_spoiler: bool = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
"types.ReplyKeyboardMarkup",
@@ -83,8 +86,15 @@ async def copy_message(
Sends the message silently.
Users will receive a notification with no sound.
+ message_thread_id (``int``, *optional*):
+ Unique identifier for the target message thread (topic) of the forum.
+ For supergroups only.
+
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -92,6 +102,9 @@ async def copy_message(
protect_content (``bool``, *optional*):
Protects the contents of the sent message from forwarding and saving.
+ has_spoiler (``bool``, *optional*):
+ True, if the message media is covered by a spoiler animation.
+
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
Additional interface options. An object for an inline keyboard, custom reply keyboard,
instructions to remove reply keyboard or to force a reply from the user.
@@ -114,8 +127,11 @@ async def copy_message(
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
+ message_thread_id=message_thread_id,
reply_to_message_id=reply_to_message_id,
+ reply_to_chat_id=reply_to_chat_id,
schedule_date=schedule_date,
protect_content=protect_content,
+ has_spoiler=has_spoiler,
reply_markup=reply_markup
)
diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py
index 4f44ff254..103810264 100644
--- a/pyrogram/methods/messages/download_media.py
+++ b/pyrogram/methods/messages/download_media.py
@@ -31,7 +31,7 @@
class DownloadMedia:
async def download_media(
self: "pyrogram.Client",
- message: Union["types.Message", str],
+ message: Union["types.Message", "types.Story", str],
file_name: str = DEFAULT_DOWNLOAD_DIR,
in_memory: bool = False,
block: bool = True,
@@ -43,8 +43,8 @@ async def download_media(
.. include:: /_includes/usable-by/users-bots.rst
Parameters:
- message (:obj:`~pyrogram.types.Message` | ``str``):
- Pass a Message containing the media, the media itself (message.audio, message.video, ...) or a file id
+ message (:obj:`~pyrogram.types.Message` | :obj:`~pyrogram.types.Story` | ``str``):
+ Pass a Message or Story containing the media, the media itself (message.audio, message.video, ...) or a file id
as string.
file_name (``str``, *optional*):
@@ -122,9 +122,11 @@ async def progress(current, total):
available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note",
"new_chat_photo")
- if isinstance(message, types.Message):
+ if isinstance(message, (types.Message, types.Story)):
+ story = getattr(message, "story", None)
+
for kind in available_media:
- media = getattr(message, kind, None)
+ media = getattr(story or message, kind, None)
if media is not None:
break
diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py
index 7ab424a4f..e06ded2a3 100644
--- a/pyrogram/methods/messages/edit_inline_media.py
+++ b/pyrogram/methods/messages/edit_inline_media.py
@@ -108,7 +108,7 @@ async def edit_inline_media(
spoiler=media.has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO)
+ media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO, has_spoiler=media.has_spoiler)
elif isinstance(media, types.InputMediaVideo):
if is_uploaded_file:
media = raw.types.InputMediaUploadedDocument(
@@ -131,7 +131,7 @@ async def edit_inline_media(
spoiler=media.has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO)
+ media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO, has_spoiler=media.has_spoiler)
elif isinstance(media, types.InputMediaAudio):
if is_uploaded_file:
media = raw.types.InputMediaUploadedDocument(
@@ -176,7 +176,7 @@ async def edit_inline_media(
spoiler=media.has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION)
+ media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION, has_spoiler=media.has_spoiler)
elif isinstance(media, types.InputMediaDocument):
if is_uploaded_file:
media = raw.types.InputMediaUploadedDocument(
diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py
index 2e4a45a69..9a8290b79 100644
--- a/pyrogram/methods/messages/edit_message_caption.py
+++ b/pyrogram/methods/messages/edit_message_caption.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
+from datetime import datetime
from typing import Union, List, Optional
import pyrogram
@@ -30,6 +31,7 @@ async def edit_message_caption(
caption: str,
parse_mode: Optional["enums.ParseMode"] = None,
caption_entities: List["types.MessageEntity"] = None,
+ schedule_date: datetime = None,
reply_markup: "types.InlineKeyboardMarkup" = None
) -> "types.Message":
"""Edit the caption of media messages.
@@ -55,6 +57,9 @@ async def edit_message_caption(
caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+ schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+ Date when the message will be automatically sent.
+
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
@@ -72,5 +77,6 @@ async def edit_message_caption(
text=caption,
parse_mode=parse_mode,
entities=caption_entities,
+ schedule_date=schedule_date,
reply_markup=reply_markup
)
diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py
index 5a34f1387..ca7552264 100644
--- a/pyrogram/methods/messages/edit_message_media.py
+++ b/pyrogram/methods/messages/edit_message_media.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
+from datetime import datetime
import io
import os
import re
@@ -34,6 +35,8 @@ async def edit_message_media(
chat_id: Union[int, str],
message_id: int,
media: "types.InputMedia",
+ invert_media: bool = None,
+ schedule_date: datetime = None,
reply_markup: "types.InlineKeyboardMarkup" = None,
file_name: str = None
) -> "types.Message":
@@ -56,6 +59,13 @@ async def edit_message_media(
media (:obj:`~pyrogram.types.InputMedia`):
One of the InputMedia objects describing an animation, audio, document, photo or video.
+ invert_media (``bool``, *optional*):
+ If True, link preview will be shown above the message text.
+ Otherwise, the link preview will be shown below the message text.
+
+ schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+ Date when the message will be automatically sent.
+
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
@@ -117,7 +127,7 @@ async def edit_message_media(
spoiler=media.has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO)
+ media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO, has_spoiler=media.has_spoiler)
elif isinstance(media, types.InputMediaVideo):
if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media):
uploaded_media = await self.invoke(
@@ -157,7 +167,7 @@ async def edit_message_media(
spoiler=media.has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO)
+ media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO, has_spoiler=media.has_spoiler)
elif isinstance(media, types.InputMediaAudio):
if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media):
media = await self.invoke(
@@ -234,7 +244,7 @@ async def edit_message_media(
spoiler=media.has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION)
+ media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION, has_spoiler=media.has_spoiler)
elif isinstance(media, types.InputMediaDocument):
if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media):
media = await self.invoke(
@@ -271,7 +281,9 @@ async def edit_message_media(
raw.functions.messages.EditMessage(
peer=await self.resolve_peer(chat_id),
id=message_id,
+ invert_media=invert_media,
media=media,
+ schedule_date=utils.datetime_to_timestamp(schedule_date),
reply_markup=await reply_markup.write(self) if reply_markup else None,
message=message,
entities=entities
diff --git a/pyrogram/methods/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py
index 1cd75c1a8..42a631cbe 100644
--- a/pyrogram/methods/messages/edit_message_reply_markup.py
+++ b/pyrogram/methods/messages/edit_message_reply_markup.py
@@ -16,11 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
+from datetime import datetime
from typing import Union
import pyrogram
from pyrogram import raw
from pyrogram import types
+from pyrogram import utils
class EditMessageReplyMarkup:
@@ -28,6 +30,7 @@ async def edit_message_reply_markup(
self: "pyrogram.Client",
chat_id: Union[int, str],
message_id: int,
+ schedule_date: datetime = None,
reply_markup: "types.InlineKeyboardMarkup" = None,
) -> "types.Message":
"""Edit only the reply markup of messages sent by the bot.
@@ -43,6 +46,9 @@ async def edit_message_reply_markup(
message_id (``int``):
Message identifier in the chat specified in chat_id.
+ schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+ Date when the message will be automatically sent.
+
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
@@ -64,6 +70,7 @@ async def edit_message_reply_markup(
raw.functions.messages.EditMessage(
peer=await self.resolve_peer(chat_id),
id=message_id,
+ schedule_date=utils.datetime_to_timestamp(schedule_date),
reply_markup=await reply_markup.write(self) if reply_markup else None,
)
)
diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py
index 540b6aa63..66868c984 100644
--- a/pyrogram/methods/messages/edit_message_text.py
+++ b/pyrogram/methods/messages/edit_message_text.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
+from datetime import datetime
from typing import Union, List, Optional
import pyrogram
@@ -33,6 +34,7 @@ async def edit_message_text(
parse_mode: Optional["enums.ParseMode"] = None,
entities: List["types.MessageEntity"] = None,
disable_web_page_preview: bool = None,
+ schedule_date: datetime = None,
reply_markup: "types.InlineKeyboardMarkup" = None
) -> "types.Message":
"""Edit the text of messages.
@@ -61,6 +63,9 @@ async def edit_message_text(
disable_web_page_preview (``bool``, *optional*):
Disables link previews for links in this message.
+ schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+ Date when the message will be automatically sent.
+
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
@@ -84,6 +89,7 @@ async def edit_message_text(
peer=await self.resolve_peer(chat_id),
id=message_id,
no_webpage=disable_web_page_preview or None,
+ schedule_date=utils.datetime_to_timestamp(schedule_date),
reply_markup=await reply_markup.write(self) if reply_markup else None,
**await utils.parse_text_entities(self, text, parse_mode, entities)
)
diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py
index 6bc12d46c..aa017ba00 100644
--- a/pyrogram/methods/messages/forward_messages.py
+++ b/pyrogram/methods/messages/forward_messages.py
@@ -54,7 +54,8 @@ async def forward_messages(
An iterable of message identifiers in the chat specified in *from_chat_id* or a single message id.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
disable_notification (``bool``, *optional*):
Sends the message silently.
diff --git a/pyrogram/methods/messages/get_chat_history.py b/pyrogram/methods/messages/get_chat_history.py
index b384c6ba8..898b57cd8 100644
--- a/pyrogram/methods/messages/get_chat_history.py
+++ b/pyrogram/methods/messages/get_chat_history.py
@@ -57,7 +57,7 @@ async def get_chat_history(
offset: int = 0,
offset_id: int = 0,
offset_date: datetime = utils.zero_datetime()
- ) -> Optional[AsyncGenerator["types.Message", None]]:
+ ) -> AsyncGenerator["types.Message", None]:
"""Get messages from a chat history.
The messages are returned in reverse chronological order.
diff --git a/pyrogram/methods/messages/get_chat_history_count.py b/pyrogram/methods/messages/get_chat_history_count.py
index 1926224b3..78baa1a57 100644
--- a/pyrogram/methods/messages/get_chat_history_count.py
+++ b/pyrogram/methods/messages/get_chat_history_count.py
@@ -50,7 +50,7 @@ async def get_chat_history_count(
Example:
.. code-block:: python
- await app.get_history_count(chat_id)
+ await app.get_chat_history_count(chat_id)
"""
r = await self.invoke(
diff --git a/pyrogram/methods/messages/get_discussion_replies.py b/pyrogram/methods/messages/get_discussion_replies.py
index dd23751bb..6454e869f 100644
--- a/pyrogram/methods/messages/get_discussion_replies.py
+++ b/pyrogram/methods/messages/get_discussion_replies.py
@@ -28,7 +28,7 @@ async def get_discussion_replies(
chat_id: Union[int, str],
message_id: int,
limit: int = 0,
- ) -> Optional[AsyncGenerator["types.Message", None]]:
+ ) -> AsyncGenerator["types.Message", None]:
"""Get the message replies of a discussion thread.
.. include:: /_includes/usable-by/users.rst
diff --git a/pyrogram/methods/messages/read_mentions.py b/pyrogram/methods/messages/read_mentions.py
new file mode 100644
index 000000000..5e29c19f1
--- /dev/null
+++ b/pyrogram/methods/messages/read_mentions.py
@@ -0,0 +1,64 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import Union
+
+import pyrogram
+from pyrogram import raw
+
+
+class ReadMentions:
+ async def read_mentions(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str],
+ topic_id: int = None
+ ) -> bool:
+ """Mark a mention in the chat as read.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+
+ topic_id (``int``, *optional*):
+ Mark as read only mentions to messages within the specified forum topic.
+ By default, no topic is applied and all mentions marked as read.
+
+ Returns:
+ ``bool`` - On success, True is returned.
+
+ Example:
+ .. code-block:: python
+
+ # Mark the chat mention as read
+ await app.read_mentions(chat_id)
+
+ # Mark the chat mention as read in specified topic
+ await app.read_mentions(chat_id, topic_id)
+ """
+ r = await self.invoke(
+ raw.functions.messages.ReadMentions(
+ peer=await self.resolve_peer(chat_id),
+ top_msg_id=topic_id
+ )
+ )
+
+ return bool(r)
diff --git a/pyrogram/methods/messages/read_reactions.py b/pyrogram/methods/messages/read_reactions.py
new file mode 100644
index 000000000..7036cdc6d
--- /dev/null
+++ b/pyrogram/methods/messages/read_reactions.py
@@ -0,0 +1,64 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import Union
+
+import pyrogram
+from pyrogram import raw
+
+
+class ReadReactions:
+ async def read_reactions(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str],
+ topic_id: bool = None
+ ) -> bool:
+ """Mark a reaction in the chat as read.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+
+ topic_id (``int``, *optional*):
+ Mark as read only reactions to messages within the specified forum topic.
+ By default, no topic is applied and all reactions marked as read.
+
+ Returns:
+ ``bool`` - On success, True is returned.
+
+ Example:
+ .. code-block:: python
+
+ # Mark the chat reaction as read
+ await app.read_reactions(chat_id)
+
+ # Mark the chat reaction as read in specified topic
+ await app.read_reactions(chat_id, topic_id)
+ """
+ r = await self.invoke(
+ raw.functions.messages.ReadReactions(
+ peer=await self.resolve_peer(chat_id),
+ top_msg_id=topic_id
+ )
+ )
+
+ return bool(r)
diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py
index f566c9815..29738942f 100644
--- a/pyrogram/methods/messages/search_global.py
+++ b/pyrogram/methods/messages/search_global.py
@@ -30,7 +30,7 @@ async def search_global(
query: str = "",
filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY,
limit: int = 0,
- ) -> Optional[AsyncGenerator["types.Message", None]]:
+ ) -> AsyncGenerator["types.Message", None]:
"""Search messages globally from all of your chats.
If you want to get the messages count only, see :meth:`~pyrogram.Client.search_global_count`.
@@ -46,7 +46,7 @@ async def search_global(
query (``str``, *optional*):
Text query string.
Use "@" to search for mentions.
-
+
filter (:obj:`~pyrogram.enums.MessagesFilter`, *optional*):
Pass a filter in order to search for specific kind of messages only.
Defaults to any message (no filter).
diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py
index 4497ff1cd..4bd5f13b8 100644
--- a/pyrogram/methods/messages/search_messages.py
+++ b/pyrogram/methods/messages/search_messages.py
@@ -67,7 +67,7 @@ async def search_messages(
filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY,
limit: int = 0,
from_user: Union[int, str] = None
- ) -> Optional[AsyncGenerator["types.Message", None]]:
+ ) -> AsyncGenerator["types.Message", None]:
"""Search for text and media messages inside a specific chat.
If you want to get the messages count only, see :meth:`~pyrogram.Client.search_messages_count`.
diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index 64fe7dae0..b213f719e 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -48,10 +48,11 @@ async def send_animation(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
protect_content: bool = None,
reply_markup: Union[
@@ -122,20 +123,26 @@ async def send_animation(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -220,7 +227,7 @@ async def progress(current, total):
spoiler=has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(animation, FileType.ANIMATION)
+ media = utils.get_input_media_from_file_id(animation, FileType.ANIMATION, has_spoiler=has_spoiler)
else:
thumb = await self.save_file(thumb)
file = await self.save_file(animation, progress=progress, progress_args=progress_args)
@@ -240,9 +247,8 @@ async def progress(current, total):
raw.types.DocumentAttributeAnimated()
]
)
-
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
while True:
try:
@@ -259,6 +265,7 @@ async def progress(current, total):
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index 5844df8db..95a20fc0d 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -46,10 +46,11 @@ async def send_audio(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
protect_content: bool = None,
reply_markup: Union[
@@ -115,20 +116,26 @@ async def send_audio(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -228,8 +235,7 @@ async def progress(current, total):
]
)
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
while True:
try:
@@ -246,6 +252,7 @@ async def progress(current, total):
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py
index 4a217e08d..d1461dc2b 100644
--- a/pyrogram/methods/messages/send_cached_media.py
+++ b/pyrogram/methods/messages/send_cached_media.py
@@ -35,12 +35,14 @@ async def send_cached_media(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
+ quote_offset: int = None,
quote_entities: List["types.MessageEntity"] = None,
schedule_date: datetime = None,
protect_content: bool = None,
+ has_spoiler: bool = None,
invert_media: bool = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
@@ -83,7 +85,7 @@ async def send_cached_media(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -91,20 +93,27 @@ async def send_cached_media(
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
protect_content (``bool``, *optional*):
Protects the contents of the sent message from forwarding and saving.
+ has_spoiler (``bool``, *optional*):
+ True, if the message media is covered by a spoiler animation.
+
invert_media (``bool``, *optional*):
- Invert media.
+ If True, link preview will be shown above the message text.
+ Otherwise, the link preview will be shown below the message text.
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
Additional interface options. An object for an inline keyboard, custom reply keyboard,
@@ -118,15 +127,13 @@ async def send_cached_media(
await app.send_cached_media("me", file_id)
"""
-
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
peer = await self.resolve_peer(chat_id)
r = await self.invoke(
raw.functions.messages.SendMedia(
peer=peer,
- media=utils.get_input_media_from_file_id(file_id),
+ media=utils.get_input_media_from_file_id(file_id, has_spoiler=has_spoiler),
silent=disable_notification or None,
invert_media=invert_media,
reply_to=utils.get_reply_to(
@@ -136,6 +143,7 @@ async def send_cached_media(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py
index fe3e40977..c2a55f8fc 100644
--- a/pyrogram/methods/messages/send_contact.py
+++ b/pyrogram/methods/messages/send_contact.py
@@ -37,12 +37,13 @@ async def send_contact(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
+ parse_mode: Optional["enums.ParseMode"] = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
- parse_mode: Optional["enums.ParseMode"] = None,
protect_content: bool = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
@@ -79,21 +80,27 @@ async def send_contact(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
- quote_text (``str``):
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -112,9 +119,7 @@ async def send_contact(
await app.send_contact("me", "+1-123-456-7890", "Name")
"""
-
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
r = await self.invoke(
raw.functions.messages.SendMedia(
@@ -134,6 +139,7 @@ async def send_contact(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py
index 6f02a973b..b0d7ebb81 100644
--- a/pyrogram/methods/messages/send_dice.py
+++ b/pyrogram/methods/messages/send_dice.py
@@ -32,11 +32,12 @@ async def send_dice(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
- quote_entities: List["types.MessageEntity"] = None,
parse_mode: Optional["enums.ParseMode"] = None,
+ quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
protect_content: bool = None,
reply_markup: Union[
@@ -69,24 +70,30 @@ async def send_dice(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -112,9 +119,7 @@ async def send_dice(
# Send a basketball
await app.send_dice(chat_id, "🏀")
"""
-
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
peer = await self.resolve_peer(chat_id)
r = await self.invoke(
@@ -129,6 +134,7 @@ async def send_dice(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index 952edb96e..887c51e03 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -44,10 +44,11 @@ async def send_document(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
protect_content: bool = None,
reply_markup: Union[
@@ -107,20 +108,26 @@ async def send_document(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -206,8 +213,7 @@ async def progress(current, total):
]
)
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
while True:
try:
@@ -224,6 +230,7 @@ async def progress(current, total):
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py
index f97474148..ee4390efd 100644
--- a/pyrogram/methods/messages/send_location.py
+++ b/pyrogram/methods/messages/send_location.py
@@ -33,12 +33,12 @@ async def send_location(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
- reply_to_story_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
quote_text: str = None,
+ parse_mode: Optional["enums.ParseMode"] = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
- parse_mode: Optional["enums.ParseMode"] = None,
protect_content: bool = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
@@ -69,21 +69,27 @@ async def send_location(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message
- quote_text (``str``):
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -102,9 +108,7 @@ async def send_location(
app.send_location("me", latitude, longitude)
"""
-
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
r = await self.invoke(
raw.functions.messages.SendMedia(
@@ -121,9 +125,9 @@ async def send_location(
reply_to_message_id=reply_to_message_id,
message_thread_id=message_thread_id,
reply_to_peer=await self.resolve_peer(reply_to_chat_id) if reply_to_chat_id else None,
- reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py
index fbf56ec02..f0df2783b 100644
--- a/pyrogram/methods/messages/send_media_group.py
+++ b/pyrogram/methods/messages/send_media_group.py
@@ -46,11 +46,12 @@ async def send_media_group(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
- quote_entities: List["types.MessageEntity"] = None,
parse_mode: Optional["enums.ParseMode"] = None,
+ quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
protect_content: bool = None,
invert_media: bool = None,
@@ -74,24 +75,30 @@ async def send_media_group(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -99,7 +106,8 @@ async def send_media_group(
Protects the contents of the sent message from forwarding and saving.
invert_media (``bool``, *optional*):
- Invert media.
+ If True, link preview will be shown above the message text.
+ Otherwise, the link preview will be shown below the message text.
Returns:
List of :obj:`~pyrogram.types.Message`: On success, a list of the sent messages is returned.
@@ -162,7 +170,7 @@ async def send_media_group(
spoiler=i.has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO)
+ media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO, has_spoiler=i.has_spoiler)
else:
media = await self.invoke(
raw.functions.messages.UploadMedia(
@@ -234,7 +242,7 @@ async def send_media_group(
spoiler=i.has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO)
+ media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO, has_spoiler=i.has_spoiler)
else:
media = await self.invoke(
raw.functions.messages.UploadMedia(
@@ -418,8 +426,7 @@ async def send_media_group(
)
)
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
peer = await self.resolve_peer(chat_id)
r = await self.invoke(
@@ -434,6 +441,7 @@ async def send_media_group(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
schedule_date=utils.datetime_to_timestamp(schedule_date),
noforwards=protect_content,
diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index 7d8ff39e8..0fa073335 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -34,11 +34,13 @@ async def send_message(
disable_web_page_preview: bool = None,
disable_notification: bool = None,
message_thread_id: int = None,
+ invert_media: bool = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
protect_content: bool = None,
reply_markup: Union[
@@ -77,20 +79,30 @@ async def send_message(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
+
+ invert_media (``bool``, *optional*):
+ If True, link preview will be shown above the message text.
+ Otherwise, the link preview will be shown below the message text.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -141,8 +153,7 @@ async def send_message(
message, entities = (await utils.parse_text_entities(self, text, parse_mode, entities)).values()
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
peer = await self.resolve_peer(chat_id)
r = await self.invoke(
@@ -150,6 +161,7 @@ async def send_message(
peer=peer,
no_webpage=disable_web_page_preview or None,
silent=disable_notification or None,
+ invert_media=invert_media or None,
reply_to=utils.get_reply_to(
reply_to_message_id=reply_to_message_id,
message_thread_id=message_thread_id,
@@ -157,6 +169,7 @@ async def send_message(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py
index 895ed6425..e706f45f7 100644
--- a/pyrogram/methods/messages/send_photo.py
+++ b/pyrogram/methods/messages/send_photo.py
@@ -42,10 +42,11 @@ async def send_photo(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
protect_content: bool = None,
reply_markup: Union[
@@ -98,20 +99,26 @@ async def send_photo(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -181,7 +188,7 @@ async def send_photo(
spoiler=has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(photo, FileType.PHOTO, ttl_seconds=ttl_seconds)
+ media = utils.get_input_media_from_file_id(photo, FileType.PHOTO, ttl_seconds=ttl_seconds, has_spoiler=has_spoiler)
else:
file = await self.save_file(photo, progress=progress, progress_args=progress_args)
media = raw.types.InputMediaUploadedPhoto(
@@ -190,8 +197,7 @@ async def send_photo(
spoiler=has_spoiler
)
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
while True:
try:
@@ -208,6 +214,7 @@ async def send_photo(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py
index 7c0808457..c39f9b57d 100644
--- a/pyrogram/methods/messages/send_poll.py
+++ b/pyrogram/methods/messages/send_poll.py
@@ -40,15 +40,16 @@ async def send_poll(
open_period: int = None,
close_date: datetime = None,
is_closed: bool = None,
- protect_content: bool = None,
disable_notification: bool = None,
+ protect_content: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
- quote_entities: List["types.MessageEntity"] = None,
parse_mode: Optional["enums.ParseMode"] = None,
+ quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
@@ -122,21 +123,27 @@ async def send_poll(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
- quote_text (``str``):
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -156,8 +163,7 @@ async def send_poll(
self, explanation, explanation_parse_mode, explanation_entities
)).values()
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
r = await self.invoke(
raw.functions.messages.SendMedia(
@@ -190,6 +196,7 @@ async def send_poll(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py
index f4d1d3434..626878331 100644
--- a/pyrogram/methods/messages/send_reaction.py
+++ b/pyrogram/methods/messages/send_reaction.py
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from typing import Union
+from typing import Union, List
import pyrogram
from pyrogram import raw
@@ -26,25 +26,30 @@ class SendReaction:
async def send_reaction(
self: "pyrogram.Client",
chat_id: Union[int, str],
- message_id: int,
- emoji: str = "",
+ message_id: int = None,
+ story_id: int = None,
+ emoji: Union[int, str, List[Union[int, str]]] = None,
big: bool = False
) -> bool:
- """Send a reaction to a message.
+ """Send a reaction to a message or story.
- .. include:: /_includes/usable-by/users.rst
+ .. include:: /_includes/usable-by/users-bots.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
- message_id (``int``):
+ message_id (``int``, *optional*):
Identifier of the message.
- emoji (``str``, *optional*):
+ story_id (``int``, *optional*):
+ Identifier of the story.
+
+ emoji (``int`` | ``str`` | List of ``int`` | ``str``, *optional*):
Reaction emoji.
- Pass "" as emoji (default) to retract the reaction.
-
+ Pass None as emoji (default) to retract the reaction.
+ Pass list of int or str to react multiple emojis.
+
big (``bool``, *optional*):
Pass True to show a bigger and longer reaction.
Defaults to False.
@@ -56,18 +61,47 @@ async def send_reaction(
.. code-block:: python
# Send a reaction
- await app.send_reaction(chat_id, message_id, "🔥")
+ await app.send_reaction(chat_id, message_id=message_id, emoji="🔥")
+
+ # Send a multiple reactions
+ await app.send_reaction(chat_id, message_id=message_id, emoji=["🔥", "❤️"])
+
+ # Send a reaction with premium emoji
+ await app.send_reaction(chat_id, message_id=message_id, emoji=5319161050128459957)
+
+ # Send a reaction to story
+ await app.send_reaction(chat_id, story_id=story_id, emoji="❤️")
# Retract a reaction
- await app.send_reaction(chat_id, message_id)
+ await app.send_reaction(chat_id, message_id=message_id)
"""
- await self.invoke(
- raw.functions.messages.SendReaction(
+ if isinstance(emoji, list):
+ emoji = [
+ raw.types.ReactionCustomEmoji(document_id=i)
+ if isinstance(i, int)
+ else raw.types.ReactionEmoji(emoticon=i)
+ for i in emoji
+ ] if emoji else None
+ else:
+ if isinstance(emoji, int):
+ emoji = [raw.types.ReactionCustomEmoji(document_id=emoji)]
+ else:
+ emoji = [raw.types.ReactionEmoji(emoticon=emoji)] if emoji else None
+
+ if story_id:
+ rpc = raw.functions.stories.SendReaction(
+ peer=await self.resolve_peer(chat_id),
+ story_id=story_id,
+ reaction=emoji[0] if emoji else None,
+ )
+ else:
+ rpc = raw.functions.messages.SendReaction(
peer=await self.resolve_peer(chat_id),
msg_id=message_id,
- reaction=[raw.types.ReactionEmoji(emoticon=emoji)] if emoji else None,
+ reaction=emoji,
big=big
)
- )
+
+ await self.invoke(rpc)
return True
diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py
index 421dcc064..a9374701a 100644
--- a/pyrogram/methods/messages/send_sticker.py
+++ b/pyrogram/methods/messages/send_sticker.py
@@ -39,12 +39,13 @@ async def send_sticker(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
+ parse_mode: Optional["enums.ParseMode"] = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
- parse_mode: Optional["enums.ParseMode"] = None,
protect_content: bool = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
@@ -78,24 +79,30 @@ async def send_sticker(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -171,8 +178,7 @@ async def send_sticker(
]
)
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
while True:
try:
@@ -189,6 +195,7 @@ async def send_sticker(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py
index b6ac6cb91..f54a11f51 100644
--- a/pyrogram/methods/messages/send_venue.py
+++ b/pyrogram/methods/messages/send_venue.py
@@ -37,11 +37,12 @@ async def send_venue(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
- quote_entities: List["types.MessageEntity"] = None,
parse_mode: Optional["enums.ParseMode"] = None,
+ quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
protect_content: bool = None,
reply_markup: Union[
@@ -86,21 +87,27 @@ async def send_venue(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message
- quote_text (``str``):
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -121,9 +128,7 @@ async def send_venue(
"me", latitude, longitude,
"Venue title", "Venue address")
"""
-
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
r = await self.invoke(
raw.functions.messages.SendMedia(
@@ -148,6 +153,7 @@ async def send_venue(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index 76dcbac1d..51809c4dc 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -49,10 +49,11 @@ async def send_video(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
protect_content: bool = None,
reply_markup: Union[
@@ -128,20 +129,26 @@ async def send_video(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -226,7 +233,7 @@ async def progress(current, total):
spoiler=has_spoiler
)
else:
- media = utils.get_input_media_from_file_id(video, FileType.VIDEO, ttl_seconds=ttl_seconds)
+ media = utils.get_input_media_from_file_id(video, FileType.VIDEO, ttl_seconds=ttl_seconds, has_spoiler=has_spoiler)
else:
thumb = await self.save_file(thumb)
file = await self.save_file(video, progress=progress, progress_args=progress_args)
@@ -247,8 +254,7 @@ async def progress(current, total):
]
)
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
while True:
try:
@@ -265,6 +271,7 @@ async def progress(current, total):
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py
index 9acf1982d..a9b9aa6fd 100644
--- a/pyrogram/methods/messages/send_video_note.py
+++ b/pyrogram/methods/messages/send_video_note.py
@@ -41,12 +41,13 @@ async def send_video_note(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
+ parse_mode: Optional["enums.ParseMode"] = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
- parse_mode: Optional["enums.ParseMode"] = None,
protect_content: bool = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
@@ -92,24 +93,30 @@ async def send_video_note(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
@@ -195,8 +202,7 @@ async def send_video_note(
]
)
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
while True:
try:
@@ -213,6 +219,7 @@ async def send_video_note(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py
index 12d0d35d5..b0c249b46 100644
--- a/pyrogram/methods/messages/send_voice.py
+++ b/pyrogram/methods/messages/send_voice.py
@@ -42,12 +42,14 @@ async def send_voice(
disable_notification: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
- reply_to_chat_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
quote_text: str = None,
quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
schedule_date: datetime = None,
protect_content: bool = None,
+ ttl_seconds: int = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
"types.ReplyKeyboardMarkup",
@@ -93,25 +95,37 @@ async def send_voice(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
+
reply_to_story_id (``int``, *optional*):
Unique identifier for the target story.
- quote_text (``str``):
+ quote_text (``str``, *optional*):
Text of the quote to be sent.
- quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
protect_content (``bool``, *optional*):
Protects the contents of the sent message from forwarding and saving.
+ ttl_seconds (``int``, *optional*):
+ Self-Destruct Timer.
+ If you set a timer, the voice note will self-destruct in *ttl_seconds*
+ seconds after it was listened.
+
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
Additional interface options. An object for an inline keyboard, custom reply keyboard,
instructions to remove reply keyboard or to force a reply from the user.
@@ -153,6 +167,9 @@ async def send_voice(
# Set voice note duration
await app.send_voice("me", "voice.ogg", duration=20)
+
+ # Send self-destructing voice note
+ await app.send_voice("me", "voice.ogg", ttl_seconds=(1 << 31) - 1)
"""
file = None
@@ -168,7 +185,8 @@ async def send_voice(
voice=True,
duration=duration
)
- ]
+ ],
+ ttl_seconds=ttl_seconds
)
elif re.match("^https?://", voice):
media = raw.types.InputMediaDocumentExternal(
@@ -186,11 +204,11 @@ async def send_voice(
voice=True,
duration=duration
)
- ]
+ ],
+ ttl_seconds=ttl_seconds
)
- if quote_text or quote_entities:
- quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
while True:
try:
@@ -207,6 +225,7 @@ async def send_voice(
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
),
random_id=self.rnd_id(),
schedule_date=utils.datetime_to_timestamp(schedule_date),
diff --git a/pyrogram/methods/messages/send_web_page.py b/pyrogram/methods/messages/send_web_page.py
new file mode 100644
index 000000000..7727f6c2b
--- /dev/null
+++ b/pyrogram/methods/messages/send_web_page.py
@@ -0,0 +1,207 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 datetime import datetime
+from typing import Union, List, Optional
+
+import pyrogram
+from pyrogram import raw, utils, enums
+from pyrogram import types
+
+class SendWebPage:
+ async def send_web_page(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str],
+ url: str,
+ text: str = None,
+ force_large_media: bool = None,
+ force_small_media: bool = None,
+ parse_mode: Optional["enums.ParseMode"] = None,
+ entities: List["types.MessageEntity"] = None,
+ disable_notification: bool = None,
+ message_thread_id: int = None,
+ invert_media: bool = None,
+ reply_to_message_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
+ reply_to_story_id: int = None,
+ quote_text: str = None,
+ quote_entities: List["types.MessageEntity"] = None,
+ quote_offset: int = None,
+ schedule_date: datetime = None,
+ protect_content: bool = None,
+ reply_markup: Union[
+ "types.InlineKeyboardMarkup",
+ "types.ReplyKeyboardMarkup",
+ "types.ReplyKeyboardRemove",
+ "types.ForceReply"
+ ] = None
+ ) -> "types.Message":
+ """Send text Web Page Preview.
+
+ .. include:: /_includes/usable-by/users-bots.rst
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+
+ url (``str``):
+ Link that will be previewed.
+
+ text (``str``, *optional*):
+ Text of the message to be sent.
+
+ parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+ By default, texts are parsed using both Markdown and HTML styles.
+ You can combine both syntaxes together.
+
+ entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ List of special entities that appear in message text, which can be specified instead of *parse_mode*.
+
+ force_large_media (``bool``, *optional*):
+ If True, media in the link preview will be larger.
+ Ignored if the URL isn't explicitly specified or media size change isn't supported for the preview.
+
+ force_small_media (``bool``, *optional*):
+ If True, media in the link preview will be smaller.
+ Ignored if the URL isn't explicitly specified or media size change isn't supported for the preview.
+
+ invert_media (``bool``, *optional*):
+ If True, link preview will be shown above the message text.
+ Otherwise, the link preview will be shown below the message text.
+
+ disable_notification (``bool``, *optional*):
+ Sends the message silently.
+ Users will receive a notification with no sound.
+
+ message_thread_id (``int``, *optional*):
+ Unique identifier for the target message thread (topic) of the forum.
+ for forum supergroups only.
+
+ reply_to_message_id (``int``, *optional*):
+ If the message is a reply, ID of the original message.
+
+ reply_to_chat_id (``int`` | ``str``, *optional*):
+ If the message is a reply, ID of the original chat.
+
+ reply_to_story_id (``int``, *optional*):
+ Unique identifier for the target story.
+
+ quote_text (``str``, *optional*):
+ Text of the quote to be sent.
+
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
+ List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+
+ quote_offset (``int``, *optional*):
+ Offset for quote in original message.
+
+ schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+ Date when the message will be automatically sent.
+
+ protect_content (``bool``, *optional*):
+ Protects the contents of the sent message from forwarding and saving.
+
+ reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
+ Additional interface options. An object for an inline keyboard, custom reply keyboard,
+ instructions to remove reply keyboard or to force a reply from the user.
+
+ Returns:
+ :obj:`~pyrogram.types.Message`: On success, the sent text message is returned.
+
+ Example:
+ .. code-block:: python
+
+ # Simple example
+ await app.send_web_page("me", "https://docs.pyrogram.org")
+
+ # Make web preview image larger
+ await app.send_web_page("me", "https://docs.pyrogram.org", force_large_media=True)
+
+ """
+
+ message, entities = (await utils.parse_text_entities(self, text, parse_mode, entities)).values()
+
+ quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
+
+ r = await self.invoke(
+ raw.functions.messages.SendMedia(
+ peer=await self.resolve_peer(chat_id),
+ silent=disable_notification or None,
+ reply_to=utils.get_reply_to(
+ reply_to_message_id=reply_to_message_id,
+ message_thread_id=message_thread_id,
+ reply_to_peer=await self.resolve_peer(reply_to_chat_id) if reply_to_chat_id else None,
+ reply_to_story_id=reply_to_story_id,
+ quote_text=quote_text,
+ quote_entities=quote_entities,
+ quote_offset=quote_offset,
+ ),
+ random_id=self.rnd_id(),
+ schedule_date=utils.datetime_to_timestamp(schedule_date),
+ reply_markup=await reply_markup.write(self) if reply_markup else None,
+ message=message,
+ media=raw.types.InputMediaWebPage(
+ url=url,
+ force_large_media=force_large_media,
+ force_small_media=force_small_media
+ ),
+ invert_media=invert_media,
+ entities=entities,
+ noforwards=protect_content
+ )
+ )
+
+ if isinstance(r, raw.types.UpdateShortSentMessage):
+ peer = await self.resolve_peer(chat_id)
+
+ peer_id = (
+ peer.user_id
+ if isinstance(peer, raw.types.InputPeerUser)
+ else -peer.chat_id
+ )
+
+ return types.Message(
+ id=r.id,
+ chat=types.Chat(
+ id=peer_id,
+ type=enums.ChatType.PRIVATE,
+ client=self
+ ),
+ text=message,
+ date=utils.timestamp_to_datetime(r.date),
+ outgoing=r.out,
+ reply_markup=reply_markup,
+ entities=[
+ types.MessageEntity._parse(None, entity, {})
+ for entity in entities
+ ] if entities else None,
+ client=self
+ )
+
+ for i in r.updates:
+ if isinstance(i, (raw.types.UpdateNewMessage,
+ raw.types.UpdateNewChannelMessage,
+ raw.types.UpdateNewScheduledMessage)):
+ return await types.Message._parse(
+ self, i.message,
+ {i.id: i for i in r.users},
+ {i.id: i for i in r.chats},
+ is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage)
+ )
diff --git a/pyrogram/methods/messages/start_bot.py b/pyrogram/methods/messages/start_bot.py
new file mode 100644
index 000000000..66a0fa535
--- /dev/null
+++ b/pyrogram/methods/messages/start_bot.py
@@ -0,0 +1,77 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import Union
+
+import pyrogram
+from pyrogram import raw
+from pyrogram import types
+
+
+class StartBot:
+ async def start_bot(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str],
+ param: str = ""
+ ) -> bool:
+ """Start bot
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier of the bot you want to be started. You can specify
+ a @username (str) or a bot ID (int).
+
+ param (``str``):
+ Text of the deep linking parameter (up to 64 characters).
+ Defaults to "" (empty string).
+
+ Returns:
+ :obj:`~pyrogram.types.Message`: On success, the sent message is returned.
+
+ Example:
+ .. code-block:: python
+
+ # Start bot
+ await app.start_bot("pyrogrambot")
+
+ # Start bot with param
+ await app.start_bot("pyrogrambot", "ref123456")
+ """
+ if not param:
+ return await self.send_message(chat_id, "/start")
+
+ peer = await self.resolve_peer(chat_id)
+
+ r = await self.invoke(
+ raw.functions.messages.StartBot(
+ bot=peer,
+ peer=peer,
+ random_id=self.rnd_id(),
+ start_param=param
+ )
+ )
+
+ for i in r.updates:
+ if isinstance(i, raw.types.UpdateNewMessage):
+ return await types.Message._parse(
+ self, i.message,
+ {i.id: i for i in r.users},
+ {i.id: i for i in r.chats}
+ )
diff --git a/pyrogram/types/user_and_chats/peer_channel.py b/pyrogram/methods/premium/__init__.py
similarity index 61%
rename from pyrogram/types/user_and_chats/peer_channel.py
rename to pyrogram/methods/premium/__init__.py
index c8c8f6d77..29699583b 100644
--- a/pyrogram/types/user_and_chats/peer_channel.py
+++ b/pyrogram/methods/premium/__init__.py
@@ -16,29 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from pyrogram import raw
-from ..object import Object
-
-
-class PeerChannel(Object):
- """A PeerChannel.
-
-
- Parameters:
- channel_id (``int``):
- Id of the channel.
- """
-
- def __init__(
- self, *,
- channel_id: int
- ):
- super().__init__()
-
- self.channel_id = channel_id
-
- @staticmethod
- def _parse(action: "raw.types.PeerChannel") -> "PeerChannel":
- return PeerChannel(
- channel_id=getattr(action, "channel_id", None)
- )
+from .apply_boost import ApplyBoost
+from .get_boosts_status import GetBoostsStatus
+from .get_boosts import GetBoosts
+
+class Premium(
+ ApplyBoost,
+ GetBoostsStatus,
+ GetBoosts
+):
+ pass
diff --git a/pyrogram/methods/stories/apply_boost.py b/pyrogram/methods/premium/apply_boost.py
similarity index 79%
rename from pyrogram/methods/stories/apply_boost.py
rename to pyrogram/methods/premium/apply_boost.py
index c7d00052b..20774ea6e 100644
--- a/pyrogram/methods/stories/apply_boost.py
+++ b/pyrogram/methods/premium/apply_boost.py
@@ -20,6 +20,7 @@
import pyrogram
from pyrogram import raw
+from pyrogram import types
class ApplyBoost:
@@ -29,14 +30,14 @@ async def apply_boost(
) -> bool:
"""Apply boost
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
Returns:
- ``str``: On success, a bool is returned.
+ :obj:`~pyrogram.types.MyBoost`: On success, a boost object is returned.
Example:
.. code-block:: python
@@ -45,9 +46,14 @@ async def apply_boost(
app.apply_boost(chat_id)
"""
r = await self.invoke(
- raw.functions.stories.ApplyBoost(
+ raw.functions.premium.ApplyBoost(
peer=await self.resolve_peer(chat_id),
)
)
- return r
\ No newline at end of file
+ return types.MyBoost._parse(
+ self,
+ r.my_boosts[0],
+ {i.id: i for i in r.users},
+ {i.id: i for i in r.chats}
+ )
diff --git a/pyrogram/methods/premium/get_boosts.py b/pyrogram/methods/premium/get_boosts.py
new file mode 100644
index 000000000..95776889b
--- /dev/null
+++ b/pyrogram/methods/premium/get_boosts.py
@@ -0,0 +1,57 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import Union
+
+import pyrogram
+from pyrogram import raw
+from pyrogram import types
+
+
+class GetBoosts:
+ async def get_boosts(
+ self: "pyrogram.Client",
+ ) -> bool:
+ """Get your boosts list
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Returns:
+ List of :obj:`~pyrogram.types.MyBoost`: On success.
+
+ Example:
+ .. code-block:: python
+
+ # get boosts list
+ app.get_boosts()
+ """
+ r = await self.invoke(
+ raw.functions.premium.GetMyBoosts()
+ )
+
+ users = {i.id: i for i in r.users}
+ chats = {i.id: i for i in r.chats}
+
+ return types.List(
+ types.MyBoost._parse(
+ self,
+ boost,
+ users,
+ chats,
+ ) for boost in r.my_boosts
+ )
diff --git a/pyrogram/methods/premium/get_boosts_status.py b/pyrogram/methods/premium/get_boosts_status.py
new file mode 100644
index 000000000..bff45b7b3
--- /dev/null
+++ b/pyrogram/methods/premium/get_boosts_status.py
@@ -0,0 +1,52 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import Union
+
+import pyrogram
+from pyrogram import raw
+from pyrogram import types
+
+
+class GetBoostsStatus:
+ async def get_boosts_status(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str]
+ ) -> bool:
+ """Get boosts status of channel
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+
+ Returns:
+ :obj:`~pyrogram.types.BoostsStatus`: On success.
+
+ Example:
+ .. code-block:: python
+
+ # get boosts list
+ app.get_boosts()
+ """
+ r = await self.invoke(
+ raw.functions.premium.GetBoostsStatus(peer=await self.resolve_peer(chat_id))
+ )
+
+ return types.BoostsStatus._parse(r)
diff --git a/pyrogram/methods/stories/__init__.py b/pyrogram/methods/stories/__init__.py
index 2e24dab38..870889c59 100644
--- a/pyrogram/methods/stories/__init__.py
+++ b/pyrogram/methods/stories/__init__.py
@@ -1,6 +1,58 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 .can_send_story import CanSendStory
+from .copy_story import CopyStory
+from .delete_stories import DeleteStories
+from .edit_story_caption import EditStoryCaption
+from .edit_story_media import EditStoryMedia
+from .edit_story_privacy import EditStoryPrivacy
+from .export_story_link import ExportStoryLink
+from .forward_story import ForwardStory
+from .get_all_stories import GetAllStories
+from .get_chat_stories import GetChatStories
+from .get_pinned_stories import GetPinnedStories
+from .get_stories import GetStories
+from .get_stories_archive import GetStoriesArchive
+from .hide_stories import HideStories
+from .increment_story_views import IncrementStoryViews
+from .pin_stories import PinStories
+from .read_stories import ReadStories
from .send_story import SendStory
class Stories(
- SendStory
+ CanSendStory,
+ CopyStory,
+ DeleteStories,
+ EditStoryCaption,
+ EditStoryMedia,
+ EditStoryPrivacy,
+ ExportStoryLink,
+ ForwardStory,
+ GetAllStories,
+ GetChatStories,
+ GetPinnedStories,
+ GetStories,
+ GetStoriesArchive,
+ HideStories,
+ IncrementStoryViews,
+ PinStories,
+ ReadStories,
+ SendStory,
):
pass
diff --git a/pyrogram/methods/stories/can_send_story.py b/pyrogram/methods/stories/can_send_story.py
index 67c5fe584..5a7c3399c 100644
--- a/pyrogram/methods/stories/can_send_story.py
+++ b/pyrogram/methods/stories/can_send_story.py
@@ -16,11 +16,10 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from typing import Union, Iterable
+from typing import Union
import pyrogram
from pyrogram import raw
-from pyrogram import types
class CanSendStory:
@@ -30,7 +29,7 @@ async def can_send_story(
) -> bool:
"""Can send story
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
@@ -51,4 +50,4 @@ async def can_send_story(
)
)
- return r
\ No newline at end of file
+ return r
diff --git a/pyrogram/methods/stories/copy_story.py b/pyrogram/methods/stories/copy_story.py
index 744a564a0..28eb60577 100644
--- a/pyrogram/methods/stories/copy_story.py
+++ b/pyrogram/methods/stories/copy_story.py
@@ -17,11 +17,10 @@
# along with Pyrogram. If not, see .
import logging
-from datetime import datetime
from typing import Union, List, Optional
import pyrogram
-from pyrogram import types, enums, utils
+from pyrogram import types, enums
log = logging.getLogger(__name__)
@@ -43,7 +42,7 @@ async def copy_story(
) -> "types.Story":
"""Copy story.
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
@@ -71,13 +70,13 @@ async def copy_story(
Story privacy.
Defaults to :obj:`~pyrogram.enums.StoriesPrivacyRules.PUBLIC`
- allowed_users (List of ``int``, *optional*):
+ allowed_users (List of ``int`` | ``str``, *optional*):
List of user_id or chat_id of chat users who are allowed to view stories.
Note: chat_id available only with :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS`.
Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.CLOSE_FRIENDS`
and :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS` only
- disallowed_users (List of ``int``, *optional*):
+ disallowed_users (List of ``int`` | ``str``, *optional*):
List of user_id whos disallow to view the stories.
Note: Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.PUBLIC`
and :obj:`~pyrogram.enums.StoriesPrivacyRules.CONTACTS` only
@@ -114,4 +113,4 @@ async def copy_story(
privacy=privacy,
allowed_users=allowed_users,
disallowed_users=disallowed_users
- )
\ No newline at end of file
+ )
diff --git a/pyrogram/methods/stories/delete_stories.py b/pyrogram/methods/stories/delete_stories.py
index f02696c21..87c29c155 100644
--- a/pyrogram/methods/stories/delete_stories.py
+++ b/pyrogram/methods/stories/delete_stories.py
@@ -31,7 +31,7 @@ async def delete_stories(
) -> List[int]:
"""Delete stories.
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
@@ -39,7 +39,7 @@ async def delete_stories(
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
- story_ids (``int`` | ``list``):
+ story_ids (``int`` | Iterable of ``int``, *optional*):
Unique identifier (int) or list of unique identifiers (list of int) for the target stories.
Returns:
@@ -64,4 +64,4 @@ async def delete_stories(
)
)
- return types.List(r)
\ No newline at end of file
+ return types.List(r)
diff --git a/pyrogram/methods/stories/edit_story_caption.py b/pyrogram/methods/stories/edit_story_caption.py
new file mode 100644
index 000000000..211ca0d3c
--- /dev/null
+++ b/pyrogram/methods/stories/edit_story_caption.py
@@ -0,0 +1,83 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import List, Union
+
+import pyrogram
+from pyrogram import enums, raw, types, utils
+
+class EditStoryCaption:
+ async def edit_story_caption(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str],
+ story_id: int,
+ caption: str,
+ parse_mode: "enums.ParseMode" = None,
+ caption_entities: List["types.MessageEntity"] = None,
+ ) -> "types.Story":
+ """Edit the caption of story.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+
+ story_id (``int``):
+ Story identifier in the chat specified in chat_id.
+
+ caption (``str``):
+ New caption of the story, 0-1024 characters.
+
+ parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+ By default, texts are parsed using both Markdown and HTML styles.
+ You can combine both syntaxes together.
+
+ caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+
+ Returns:
+ :obj:`~pyrogram.types.Story`: On success, the edited story is returned.
+
+ Example:
+ .. code-block:: python
+
+ await app.edit_story(chat_id, story_id, "new media caption")
+ """
+
+ message, entities = (await utils.parse_text_entities(self, caption, parse_mode, caption_entities)).values()
+
+ r = await self.invoke(
+ raw.functions.stories.EditStory(
+ peer=await self.resolve_peer(chat_id),
+ id=story_id,
+ caption=message,
+ entities=entities,
+ )
+ )
+
+ for i in r.updates:
+ if isinstance(i, raw.types.UpdateStory):
+ return await types.Story._parse(
+ self,
+ i.story,
+ {i.id: i for i in r.users},
+ {i.id: i for i in r.chats},
+ i.peer
+ )
diff --git a/pyrogram/methods/stories/edit_story.py b/pyrogram/methods/stories/edit_story_media.py
similarity index 55%
rename from pyrogram/methods/stories/edit_story.py
rename to pyrogram/methods/stories/edit_story_media.py
index df10e6613..70a0f301a 100644
--- a/pyrogram/methods/stories/edit_story.py
+++ b/pyrogram/methods/stories/edit_story_media.py
@@ -17,56 +17,45 @@
# along with Pyrogram. If not, see .
import os
-import re
-from typing import List, Union, BinaryIO, Callable
+from typing import Union, BinaryIO, Callable
import pyrogram
-from pyrogram import enums, raw, types, utils, StopTransmission
+from pyrogram import raw, types, utils, StopTransmission
from pyrogram.errors import FilePartMissing
-class EditStory:
- async def edit_story(
+class EditStoryMedia:
+ async def edit_story_media(
self: "pyrogram.Client",
chat_id: Union[int, str],
story_id: int,
media: Union[str, BinaryIO] = None,
- caption: str = None,
duration: int = 0,
width: int = 0,
height: int = 0,
thumb: Union[str, BinaryIO] = None,
supports_streaming: bool = True,
file_name: str = None,
- privacy: "enums.StoriesPrivacyRules" = None,
- allowed_users: List[Union[int, str]] = None,
- disallowed_users: List[Union[int, str]] = None,
- parse_mode: "enums.ParseMode" = None,
- caption_entities: List["types.MessageEntity"] = None,
progress: Callable = None,
progress_args: tuple = ()
) -> "types.Story":
- """Edit story.
+ """Edit story media.
.. include:: /_includes/usable-by/users.rst
- Note: You must pass one of following paramater *animation*, *photo*, *video*
-
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
- For a contact that exists in your Telegram address book you can use his phone number (str).
+
+ story_id (``int``):
+ Story identifier in the chat specified in chat_id.
media (``str`` | ``BinaryIO``, *optional*):
Video or photo to send.
Pass a file_id as string to send a animation that exists on the Telegram servers,
- pass an HTTP URL as a string for Telegram to get a animation from the Internet,
pass a file path as string to upload a new animation that exists on your local machine, or
pass a binary file-like object with its attribute ".name" set for in-memory uploads.
- caption (``str``, *optional*):
- Story caption, 0-1024 characters.
-
duration (``int``, *optional*):
Duration of sent video in seconds.
@@ -82,28 +71,6 @@ async def edit_story(
A thumbnail's width and height should not exceed 320 pixels.
Thumbnails can't be reused and can be only uploaded as a new file.
- privacy (:obj:`~pyrogram.enums.StoriesPrivacyRules`, *optional*):
- Story privacy.
- Defaults to :obj:`~pyrogram.enums.StoriesPrivacyRules.PUBLIC`
-
- allowed_users (List of ``int``, *optional*):
- List of user_id or chat_id of chat users who are allowed to view stories.
- Note: chat_id available only with :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS`.
- Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.CLOSE_FRIENDS`
- and :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS` only
-
- disallowed_users (List of ``int``, *optional*):
- List of user_id whos disallow to view the stories.
- Note: Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.PUBLIC`
- and :obj:`~pyrogram.enums.StoriesPrivacyRules.CONTACTS` only
-
- parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
- By default, texts are parsed using both Markdown and HTML styles.
- You can combine both syntaxes together.
-
- caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
- List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
-
progress (``Callable``, *optional*):
Pass a callback function to view the file transmission progress.
The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
@@ -116,25 +83,19 @@ async def edit_story(
object or a Client instance in order to edit the message with the updated progress status.
Returns:
- :obj:`~pyrogram.types.Story` a single story is returned.
+ :obj:`~pyrogram.types.Story`: On success, the edited story is returned.
Example:
.. code-block:: python
- # Send new photo story
- photo_id = "abcd12345"
- await app.edit_story(meida=photo_id, caption='Hello guys.')
+ # Replace the current media with a local photo
+ await app.edit_story_media(chat_id, story_id, "new_photo.jpg")
- Raises:
- ValueError: In case of invalid arguments.
+ # Replace the current media with a local video
+ await app.edit_story_media(chat_id, story_id, "new_video.mp4")
"""
# TODO: media_areas
- if privacy:
- privacy_rules = [types.StoriesPrivacyRules(type=privacy)]
-
- message, entities = (await utils.parse_text_entities(self, caption, parse_mode, caption_entities)).values()
-
try:
if isinstance(media, str):
if os.path.isfile(media):
@@ -159,16 +120,6 @@ async def edit_story(
media = raw.types.InputMediaUploadedPhoto(
file=file,
)
- elif re.match("^https?://", media):
- mime_type = self.guess_mime_type(media)
- if mime_type == "video/mp4":
- media = raw.types.InputMediaDocumentExternal(
- url=media,
- )
- else:
- media = raw.types.InputMediaPhotoExternal(
- url=media,
- )
else:
media = utils.get_input_media_from_file_id(media)
else:
@@ -195,42 +146,6 @@ async def edit_story(
file=file,
)
- privacy_rules = []
-
- if privacy:
- if privacy == enums.StoriesPrivacyRules.PUBLIC:
- privacy_rules.append(raw.types.InputPrivacyValueAllowAll())
- if disallowed_users:
- users = [await self.resolve_peer(user_id) for user_id in disallowed_users]
- privacy_rules.append(raw.types.InputPrivacyValueDisallowUsers(users=users))
- elif privacy == enums.StoriesPrivacyRules.CONTACTS:
- privacy_rules = [raw.types.InputPrivacyValueAllowContacts()]
- if disallowed_users:
- users = [await self.resolve_peer(user_id) for user_id in disallowed_users]
- privacy_rules.append(raw.types.InputPrivacyValueDisallowUsers(users=users))
- elif privacy == enums.StoriesPrivacyRules.CLOSE_FRIENDS:
- privacy_rules = [raw.types.InputPrivacyValueAllowCloseFriends()]
- if allowed_users:
- users = [await self.resolve_peer(user_id) for user_id in allowed_users]
- privacy_rules.append(raw.types.InputPrivacyValueAllowUsers(users=users))
- elif privacy == enums.StoriesPrivacyRules.SELECTED_USERS:
- _allowed_users = []
- _allowed_chats = []
-
- for user in allowed_users:
- peer = await self.resolve_peer(user)
- if isinstance(peer, raw.types.InputPeerUser):
- _allowed_users.append(peer)
- elif isinstance(peer, raw.types.InputPeerChat):
- _allowed_chats.append(peer)
-
- if _allowed_users:
- privacy_rules.append(raw.types.InputPrivacyValueAllowUsers(users=_allowed_users))
- if _allowed_chats:
- privacy_rules.append(raw.types.InputPrivacyValueAllowChatParticipants(chats=_allowed_chats))
- else:
- privacy_rules.append(raw.types.InputPrivacyValueAllowAll())
-
while True:
try:
r = await self.invoke(
@@ -238,9 +153,6 @@ async def edit_story(
peer=await self.resolve_peer(chat_id),
id=story_id,
media=media,
- caption=message,
- entities=entities,
- privacy_rules=privacy_rules,
)
)
except FilePartMissing as e:
diff --git a/pyrogram/methods/stories/edit_story_privacy.py b/pyrogram/methods/stories/edit_story_privacy.py
new file mode 100644
index 000000000..a57adc50a
--- /dev/null
+++ b/pyrogram/methods/stories/edit_story_privacy.py
@@ -0,0 +1,130 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import List, Union
+
+import pyrogram
+from pyrogram import enums, raw, types
+
+class EditStoryPrivacy:
+ async def edit_story_privacy(
+ self: "pyrogram.Client",
+ chat_id: Union[int, str],
+ story_id: int,
+ privacy: "enums.StoriesPrivacyRules" = None,
+ allowed_users: List[Union[int, str]] = None,
+ disallowed_users: List[Union[int, str]] = None,
+ ) -> "types.Story":
+ """Edit the privacy of story.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+
+ story_id (``int``):
+ Story identifier in the chat specified in chat_id.
+
+ privacy (:obj:`~pyrogram.enums.StoriesPrivacyRules`, *optional*):
+ Story privacy.
+
+ allowed_users (List of ``int`` | ``str``, *optional*):
+ List of user_id or chat_id of chat users who are allowed to view stories.
+ Note: chat_id available only with :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS`.
+ Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.CLOSE_FRIENDS`
+ and :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS` only
+
+ disallowed_users (List of ``int`` | ``str``, *optional*):
+ List of user_id whos disallow to view the stories.
+ Note: Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.PUBLIC`
+ and :obj:`~pyrogram.enums.StoriesPrivacyRules.CONTACTS` only
+
+ Returns:
+ :obj:`~pyrogram.types.Story`: On success, the edited story is returned.
+
+ Example:
+ .. code-block:: python
+
+ # Edit story privacy to public
+ await app.edit_story_privacy(chat_id, story_id, enums.StoriesPrivacyRules.PUBLIC)
+
+ # Edit the privacy of the story to allow selected users to view the story
+ await app.edit_story_privacy(
+ chat_id,
+ story_id,
+ enums.StoriesPrivacyRules.SELECTED_USERS,
+ allowed_users=[123, 456]
+ )
+ """
+ privacy_rules = []
+
+ if privacy:
+ if privacy == enums.StoriesPrivacyRules.PUBLIC:
+ privacy_rules.append(raw.types.InputPrivacyValueAllowAll())
+ if disallowed_users:
+ users = [await self.resolve_peer(user_id) for user_id in disallowed_users]
+ privacy_rules.append(raw.types.InputPrivacyValueDisallowUsers(users=users))
+ elif privacy == enums.StoriesPrivacyRules.CONTACTS:
+ privacy_rules = [raw.types.InputPrivacyValueAllowContacts()]
+ if disallowed_users:
+ users = [await self.resolve_peer(user_id) for user_id in disallowed_users]
+ privacy_rules.append(raw.types.InputPrivacyValueDisallowUsers(users=users))
+ elif privacy == enums.StoriesPrivacyRules.CLOSE_FRIENDS:
+ privacy_rules = [raw.types.InputPrivacyValueAllowCloseFriends()]
+ if allowed_users:
+ users = [await self.resolve_peer(user_id) for user_id in allowed_users]
+ privacy_rules.append(raw.types.InputPrivacyValueAllowUsers(users=users))
+ elif privacy == enums.StoriesPrivacyRules.SELECTED_USERS:
+ _allowed_users = []
+ _allowed_chats = []
+
+ for user in allowed_users:
+ peer = await self.resolve_peer(user)
+ if isinstance(peer, raw.types.InputPeerUser):
+ _allowed_users.append(peer)
+ elif isinstance(peer, (raw.types.InputPeerChat, raw.types.InputPeerChannel)):
+ _allowed_chats.append(peer)
+
+ if _allowed_users:
+ privacy_rules.append(raw.types.InputPrivacyValueAllowUsers(users=_allowed_users))
+ if _allowed_chats:
+ privacy_rules.append(raw.types.InputPrivacyValueAllowChatParticipants(chats=_allowed_chats))
+ else:
+ privacy_rules.append(raw.types.InputPrivacyValueAllowAll())
+
+
+ r = await self.invoke(
+ raw.functions.stories.EditStory(
+ peer=await self.resolve_peer(chat_id),
+ id=story_id,
+ privacy_rules=privacy_rules,
+ )
+ )
+
+ for i in r.updates:
+ if isinstance(i, raw.types.UpdateStory):
+ return await types.Story._parse(
+ self,
+ i.story,
+ {i.id: i for i in r.users},
+ {i.id: i for i in r.chats},
+ i.peer
+ )
diff --git a/pyrogram/methods/stories/export_story_link.py b/pyrogram/methods/stories/export_story_link.py
index ee4f4d3c5..c7e48e81c 100644
--- a/pyrogram/methods/stories/export_story_link.py
+++ b/pyrogram/methods/stories/export_story_link.py
@@ -20,7 +20,6 @@
import pyrogram
from pyrogram import raw
-from pyrogram import types
class ExportStoryLink:
@@ -28,10 +27,10 @@ async def export_story_link(
self: "pyrogram.Client",
chat_id: Union[int, str],
story_id: int,
- ) -> "types.ExportedStoryLink":
+ ) -> str:
"""Export a story link.
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
@@ -58,4 +57,4 @@ async def export_story_link(
)
)
- return r.link
\ No newline at end of file
+ return r.link
diff --git a/pyrogram/methods/stories/forward_story.py b/pyrogram/methods/stories/forward_story.py
index e2c3bd844..9bd6358c5 100644
--- a/pyrogram/methods/stories/forward_story.py
+++ b/pyrogram/methods/stories/forward_story.py
@@ -34,5 +34,68 @@ async def forward_story(
message_thread_id: int = None,
schedule_date: datetime = None,
) -> Optional["types.Message"]:
- #TODO
- pass
\ No newline at end of file
+ """Send story.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+
+ from_chat_id (``int`` | ``str``):
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+
+ story_id (``int``):
+ Unique identifier of story.
+
+ disable_notification (``bool``, *optional*):
+ Sends the message with story silently.
+ Users will receive a notification with no sound.
+
+ message_thread_id (``int``, *optional*):
+ Unique identifier for the target message thread (topic) of the forum.
+ For supergroups only.
+
+ schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+ Date when the message will be automatically sent.
+
+ Returns:
+ :obj:`~pyrogram.types.Message`: On success, the sent story message is returned.
+
+ Example:
+ .. code-block:: python
+
+ # Send your story to chat_id
+ await app.forward_story(to_chat, from_chat, 123)
+ """
+ r = await self.invoke(
+ raw.functions.messages.SendMedia(
+ peer=await self.resolve_peer(chat_id),
+ media=raw.types.InputMediaStory(
+ peer=await self.resolve_peer(from_chat_id),
+ id=story_id
+ ),
+ silent=disable_notification or None,
+ random_id=self.rnd_id(),
+ schedule_date=utils.datetime_to_timestamp(schedule_date),
+ message="",
+ reply_to=utils.get_reply_to(
+ message_thread_id=message_thread_id
+ ),
+ )
+ )
+
+ for i in r.updates:
+ if isinstance(i, (raw.types.UpdateNewMessage,
+ raw.types.UpdateNewChannelMessage,
+ raw.types.UpdateNewScheduledMessage)):
+ return await types.Message._parse(
+ self, i.message,
+ {i.id: i for i in r.users},
+ {i.id: i for i in r.chats},
+ is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage)
+ )
diff --git a/pyrogram/methods/stories/get_all_stories.py b/pyrogram/methods/stories/get_all_stories.py
index ef0456502..20c1b00e1 100644
--- a/pyrogram/methods/stories/get_all_stories.py
+++ b/pyrogram/methods/stories/get_all_stories.py
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from typing import AsyncGenerator, Union, Optional
+from typing import AsyncGenerator, Optional
import pyrogram
from pyrogram import raw
@@ -29,7 +29,7 @@ async def get_all_stories(
next: Optional[bool] = None,
hidden: Optional[bool] = None,
state: Optional[str] = None,
- ) -> Optional[AsyncGenerator["types.Story", None]]:
+ ) -> AsyncGenerator["types.Story", None]:
"""Get all active stories.
.. include:: /_includes/usable-by/users.rst
@@ -56,12 +56,15 @@ async def get_all_stories(
)
)
+ users = {i.id: i for i in r.users}
+ chats = {i.id: i for i in r.chats}
+
for peer_story in r.peer_stories:
for story in peer_story.stories:
yield await types.Story._parse(
self,
story,
- {i.id: i for i in r.users},
- {i.id: i for i in r.chats},
+ users,
+ chats,
peer_story.peer
- )
\ No newline at end of file
+ )
diff --git a/pyrogram/methods/stories/get_peer_stories.py b/pyrogram/methods/stories/get_chat_stories.py
similarity index 79%
rename from pyrogram/methods/stories/get_peer_stories.py
rename to pyrogram/methods/stories/get_chat_stories.py
index 3640eda78..1ea5d89c3 100644
--- a/pyrogram/methods/stories/get_peer_stories.py
+++ b/pyrogram/methods/stories/get_chat_stories.py
@@ -16,19 +16,19 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from typing import AsyncGenerator, Union, Optional
+from typing import AsyncGenerator, Union
import pyrogram
from pyrogram import raw
from pyrogram import types
-class GetPeerStories:
- async def get_peer_stories(
+class GetChatStories:
+ async def get_chat_stories(
self: "pyrogram.Client",
chat_id: Union[int, str]
- ) -> Optional[AsyncGenerator["types.Story", None]]:
- """Get all active stories from an user by using user identifiers.
+ ) -> AsyncGenerator["types.Story", None]:
+ """Get all non expired stories from a chat by using chat identifier.
.. include:: /_includes/usable-by/users.rst
@@ -44,8 +44,8 @@ async def get_peer_stories(
Example:
.. code-block:: python
- # Get all active story from spesific user
- async for story in app.get_peer_stories(chat_id):
+ # Get all non expired stories from spesific chat
+ async for story in app.get_chat_stories(chat_id):
print(story)
Raises:
@@ -59,11 +59,14 @@ async def get_peer_stories(
)
)
+ users = {i.id: i for i in r.users}
+ chats = {i.id: i for i in r.chats}
+
for story in r.stories.stories:
yield await types.Story._parse(
self,
story,
- {i.id: i for i in r.users},
- {i.id: i for i in r.chats},
+ users,
+ chats,
r.stories.peer
- )
\ No newline at end of file
+ )
diff --git a/pyrogram/methods/stories/get_pinned_stories.py b/pyrogram/methods/stories/get_pinned_stories.py
index d62f31407..347bf98dd 100644
--- a/pyrogram/methods/stories/get_pinned_stories.py
+++ b/pyrogram/methods/stories/get_pinned_stories.py
@@ -16,11 +16,12 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from typing import AsyncGenerator, Union, Optional
+from typing import AsyncGenerator, Union
import pyrogram
from pyrogram import raw
from pyrogram import types
+from pyrogram import utils
class GetPinnedStories:
@@ -29,8 +30,8 @@ async def get_pinned_stories(
chat_id: Union[int, str],
offset_id: int = 0,
limit: int = 0,
- ) -> Optional[AsyncGenerator["types.Story", None]]:
- """Get pinned stories stories.
+ ) -> AsyncGenerator["types.Story", None]:
+ """Get all pinned stories from a chat by using chat identifier.
.. include:: /_includes/usable-by/users.rst
@@ -53,15 +54,16 @@ async def get_pinned_stories(
.. code-block:: python
# Get all pinned story
- async for story in app.get_pinned_stories():
+ async for story in app.get_pinned_stories(chat_id):
print(story)
"""
current = 0
total = abs(limit) or (1 << 31)
limit = min(100, total)
+ peer = await self.resolve_peer(chat_id)
+
while True:
- peer = await self.resolve_peer(chat_id)
r = await self.invoke(
raw.functions.stories.GetPinnedStories(
peer=peer,
@@ -73,6 +75,15 @@ async def get_pinned_stories(
if not r.stories:
return
+ users = {i.id: i for i in r.users}
+ chats = {i.id: i for i in r.chats}
+
+ if isinstance(peer, raw.types.InputPeerChannel):
+ peer_id = utils.get_raw_peer_id(peer)
+ if peer_id not in r.chats:
+ channel = await self.invoke(raw.functions.channels.GetChannels(id=[peer]))
+ chats.update({peer_id: channel.chats[0]})
+
last = r.stories[-1]
offset_id = last.id
@@ -80,12 +91,12 @@ async def get_pinned_stories(
yield await types.Story._parse(
self,
story,
- {i.id: i for i in r.users},
- {i.id: i for i in r.chats},
+ users,
+ chats,
peer
)
current += 1
if current >= total:
- return
\ No newline at end of file
+ return
diff --git a/pyrogram/methods/stories/get_stories.py b/pyrogram/methods/stories/get_stories.py
index 997eec044..40ae2922b 100644
--- a/pyrogram/methods/stories/get_stories.py
+++ b/pyrogram/methods/stories/get_stories.py
@@ -29,9 +29,9 @@ async def get_stories(
chat_id: Union[int, str],
story_ids: Union[int, Iterable[int]],
) -> "types.Stories":
- """Get stories by id.
+ """Get one or more stories from a chat by using stories identifiers.
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
@@ -39,7 +39,7 @@ async def get_stories(
For your personal story you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
- story_ids (List of ``int`` ``32-bit``):
+ story_ids (``int`` | Iterable of ``int``, *optional*):
Pass a single story identifier or an iterable of story ids (as integers) to get the content of the
story themselves.
@@ -51,7 +51,7 @@ async def get_stories(
.. code-block:: python
# Get stories by id
- stories = await app.get_stories_by_id(chat_id, [1, 2, 3])
+ stories = await app.get_stories(chat_id, [1, 2, 3])
for story in stories:
print(story)
@@ -69,15 +69,18 @@ async def get_stories(
stories = []
+ users = {i.id: i for i in r.users}
+ chats = {i.id: i for i in r.chats}
+
for story in r.stories:
stories.append(
await types.Story._parse(
self,
story,
- {i.id: i for i in r.users},
- {i.id: i for i in r.chats},
+ users,
+ chats,
peer
)
)
- return types.List(stories) if is_iterable else stories[0]
\ No newline at end of file
+ return types.List(stories) if is_iterable else stories[0] if stories else None
diff --git a/pyrogram/methods/stories/get_stories_archive.py b/pyrogram/methods/stories/get_stories_archive.py
index df0bedf8c..49c70ca31 100644
--- a/pyrogram/methods/stories/get_stories_archive.py
+++ b/pyrogram/methods/stories/get_stories_archive.py
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from typing import AsyncGenerator, Union, Optional
+from typing import AsyncGenerator, Union
import pyrogram
from pyrogram import raw
@@ -29,8 +29,8 @@ async def get_stories_archive(
chat_id: Union[int, str],
limit: int = 0,
offset_id: int = 0
- ) -> Optional[AsyncGenerator["types.Story", None]]:
- """Get stories archive.
+ ) -> AsyncGenerator["types.Story", None]:
+ """Get all archived stories from a chat by using chat identifier.
.. include:: /_includes/usable-by/users.rst
@@ -77,16 +77,19 @@ async def get_stories_archive(
last = r.stories[-1]
offset_id = last.id
+ users = {i.id: i for i in r.users}
+ chats = {i.id: i for i in r.chats}
+
for story in r.stories:
yield await types.Story._parse(
self,
story,
- {i.id: i for i in r.users},
- {i.id: i for i in r.chats},
+ users,
+ chats,
peer
)
current += 1
if current >= total:
- return
\ No newline at end of file
+ return
diff --git a/pyrogram/methods/stories/hide_stories.py b/pyrogram/methods/stories/hide_stories.py
index 1664b9525..e8ec242b4 100644
--- a/pyrogram/methods/stories/hide_stories.py
+++ b/pyrogram/methods/stories/hide_stories.py
@@ -30,7 +30,7 @@ async def hide_stories(
) -> bool:
"""Toggle peer stories hidden
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
@@ -54,4 +54,4 @@ async def hide_stories(
)
)
- return r
\ No newline at end of file
+ return r
diff --git a/pyrogram/methods/stories/increment_story_views.py b/pyrogram/methods/stories/increment_story_views.py
index 345e0f136..c0fa937c7 100644
--- a/pyrogram/methods/stories/increment_story_views.py
+++ b/pyrogram/methods/stories/increment_story_views.py
@@ -30,12 +30,11 @@ async def increment_story_views(
) -> bool:
"""Increment story views.
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
- For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
story_id (``int``):
@@ -57,4 +56,4 @@ async def increment_story_views(
)
)
- return r
\ No newline at end of file
+ return r
diff --git a/pyrogram/methods/stories/pin_stories.py b/pyrogram/methods/stories/pin_stories.py
index 3b1dbf6d5..530352c82 100644
--- a/pyrogram/methods/stories/pin_stories.py
+++ b/pyrogram/methods/stories/pin_stories.py
@@ -30,16 +30,16 @@ async def pin_stories(
stories_ids: Union[int, Iterable[int]],
pinned: bool = False,
) -> List[int]:
- """Toggle stories pinned.
+ """Pin one or more stories in a chat by using stories identifiers.
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
- stories_ids (List of ``int`` ``32-bit``):
+ stories_ids (``int`` | Iterable of ``int``, *optional*):
List of unique identifiers of the target stories.
pinned (``bool``):
@@ -66,4 +66,4 @@ async def pin_stories(
)
)
- return types.List(r)
\ No newline at end of file
+ return types.List(r)
diff --git a/pyrogram/methods/stories/read_stories.py b/pyrogram/methods/stories/read_stories.py
index c7686d30f..2ee52b581 100644
--- a/pyrogram/methods/stories/read_stories.py
+++ b/pyrogram/methods/stories/read_stories.py
@@ -30,16 +30,16 @@ async def read_stories(
) -> List[int]:
"""Read stories.
- .. include:: /_includes/usable-by/users-bots.rst
+ .. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
- For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
max_id (``int``, *optional*):
- Maximum identifier of the target story to read.
+ The id of the last story you want to mark as read; all the stories before this one will be marked as
+ read as well. Defaults to 0 (mark every unread message as read).
Returns:
List of ``int``: On success, a list of read stories is returned.
@@ -47,14 +47,17 @@ async def read_stories(
Example:
.. code-block:: python
- # Read stories
+ # Read all stories
await app.read_stories(chat_id)
+
+ # Mark stories as read only up to the given story id
+ await app.read_stories(chat_id, 123)
"""
r = await self.invoke(
raw.functions.stories.ReadStories(
peer=await self.resolve_peer(chat_id),
- max_id=max_id
+ max_id=max_id or (1 << 31) - 1
)
)
- return types.List(r)
\ No newline at end of file
+ return types.List(r)
diff --git a/pyrogram/methods/stories/send_story.py b/pyrogram/methods/stories/send_story.py
index 28df3728b..f82440428 100644
--- a/pyrogram/methods/stories/send_story.py
+++ b/pyrogram/methods/stories/send_story.py
@@ -27,8 +27,8 @@
class SendStory:
async def send_story(
self: "pyrogram.Client",
+ chat_id: Union[int, str],
media: Union[str, BinaryIO],
- chat_id: Union[int, str]="me",
caption: str = None,
period: int = None,
duration: int = 0,
@@ -52,7 +52,7 @@ async def send_story(
.. include:: /_includes/usable-by/users.rst
- Note: You must pass one of following paramater *animation*, *photo*, *video*
+ Note: You must pass one of following parameters *animation*, *photo*, *video*
Parameters:
chat_id (``int`` | ``str``):
@@ -99,7 +99,7 @@ async def send_story(
and :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS` only
disallowed_users (List of ``int``, *optional*):
- List of user_id whos disallow to view the stories.
+ List of user_id who are disallowed to view the stories.
Note: Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.PUBLIC`
and :obj:`~pyrogram.enums.StoriesPrivacyRules.CONTACTS` only
@@ -135,8 +135,11 @@ async def send_story(
Example:
.. code-block:: python
- # Send new story
- await app.send_story(media=file_id, caption='Hello guys.')
+ # Post story to your profile
+ await app.send_story("me", "story.png", caption='My new story!')
+
+ # Post story to channel
+ await app.send_story(123456, "story.png", caption='My new story!')
Raises:
ValueError: In case of invalid arguments.
@@ -240,7 +243,7 @@ async def send_story(
peer = await self.resolve_peer(user)
if isinstance(peer, raw.types.InputPeerUser):
_allowed_users.append(peer)
- elif isinstance(peer, raw.types.InputPeerChat):
+ elif isinstance(peer, (raw.types.InputPeerChat, raw.types.InputPeerChannel)):
_allowed_chats.append(peer)
if _allowed_users:
@@ -279,4 +282,4 @@ async def send_story(
i.peer
)
except StopTransmission:
- return None
\ No newline at end of file
+ return None
diff --git a/pyrogram/methods/users/__init__.py b/pyrogram/methods/users/__init__.py
index 31fda1dc3..b1b8bc71a 100644
--- a/pyrogram/methods/users/__init__.py
+++ b/pyrogram/methods/users/__init__.py
@@ -29,6 +29,7 @@
from .set_username import SetUsername
from .unblock_user import UnblockUser
from .update_profile import UpdateProfile
+from .update_status import UpdateStatus
class Users(
@@ -43,6 +44,7 @@ class Users(
GetChatPhotosCount,
UnblockUser,
UpdateProfile,
+ UpdateStatus,
GetDefaultEmojiStatuses,
SetEmojiStatus
):
diff --git a/pyrogram/methods/users/get_chat_photos.py b/pyrogram/methods/users/get_chat_photos.py
index d22c68dcd..05dfd92d9 100644
--- a/pyrogram/methods/users/get_chat_photos.py
+++ b/pyrogram/methods/users/get_chat_photos.py
@@ -27,7 +27,7 @@ async def get_chat_photos(
self: "pyrogram.Client",
chat_id: Union[int, str],
limit: int = 0,
- ) -> Optional[AsyncGenerator["types.Photo", None]]:
+ ) -> AsyncGenerator["types.Photo", None]:
"""Get a chat or a user profile photos sequentially.
.. include:: /_includes/usable-by/users-bots.rst
diff --git a/pyrogram/methods/users/update_status.py b/pyrogram/methods/users/update_status.py
new file mode 100644
index 000000000..2119f3f54
--- /dev/null
+++ b/pyrogram/methods/users/update_status.py
@@ -0,0 +1,54 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 pyrogram import raw
+
+
+class UpdateStatus:
+ async def update_status(
+ self: "pyrogram.Client",
+ offline: bool = False
+ ) -> bool:
+ """Update your profile status.
+
+ .. include:: /_includes/usable-by/users.rst
+
+ Parameters:
+ offline (``bool``):
+ The new status. Pass True to appear offline.
+
+ Returns:
+ ``bool``: True on success.
+
+ Example:
+ .. code-block:: python
+
+ # Change status to online
+ await app.update_status()
+
+ # Change status to offline
+ await app.update_status(offline=True)
+ """
+ r = await self.invoke(
+ raw.functions.account.UpdateStatus(
+ offline=offline
+ )
+ )
+
+ return bool(r)
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 0ed967a1a..c140fa8b3 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -19,6 +19,7 @@
import asyncio
import bisect
import logging
+import datetime
import os
from hashlib import sha1
from io import BytesIO
@@ -59,6 +60,9 @@ class Session:
444: "invalid DC"
}
+ # last time used date of this session.
+ last_used_time: datetime.datetime = None
+
def __init__(
self,
client: "pyrogram.Client",
diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py
index aebe91767..1c77688c5 100644
--- a/pyrogram/storage/file_storage.py
+++ b/pyrogram/storage/file_storage.py
@@ -25,6 +25,17 @@
log = logging.getLogger(__name__)
+SCHEMA = """
+CREATE TABLE usernames
+(
+ id INTEGER,
+ username TEXT,
+ FOREIGN KEY (id) REFERENCES peers(id)
+);
+
+CREATE INDEX idx_usernames_username ON usernames (username);
+"""
+
class FileStorage(SQLiteStorage):
FILE_EXTENSION = ".session"
@@ -49,6 +60,12 @@ def update(self):
version += 1
+ if version == 3:
+ with self.conn:
+ self.conn.executescript(SCHEMA)
+
+ version += 1
+
self.version(version)
async def open(self):
diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py
index e28b9b746..d249d765a 100644
--- a/pyrogram/storage/sqlite_storage.py
+++ b/pyrogram/storage/sqlite_storage.py
@@ -43,19 +43,25 @@
id INTEGER PRIMARY KEY,
access_hash INTEGER,
type INTEGER NOT NULL,
- username TEXT,
phone_number TEXT,
last_update_on INTEGER NOT NULL DEFAULT (CAST(STRFTIME('%s', 'now') AS INTEGER))
);
+CREATE TABLE usernames
+(
+ id INTEGER,
+ username TEXT,
+ FOREIGN KEY (id) REFERENCES peers(id)
+);
+
CREATE TABLE version
(
number INTEGER PRIMARY KEY
);
CREATE INDEX idx_peers_id ON peers (id);
-CREATE INDEX idx_peers_username ON peers (username);
CREATE INDEX idx_peers_phone_number ON peers (phone_number);
+CREATE INDEX idx_usernames_username ON usernames (username);
CREATE TRIGGER trg_peers_last_update_on
AFTER UPDATE
@@ -90,7 +96,7 @@ def get_input_peer(peer_id: int, access_hash: int, peer_type: str):
class SQLiteStorage(Storage):
- VERSION = 3
+ VERSION = 4
USERNAME_TTL = 8 * 60 * 60
def __init__(self, name: str):
@@ -125,12 +131,25 @@ async def close(self):
async def delete(self):
raise NotImplementedError
- async def update_peers(self, peers: List[Tuple[int, int, str, str, str]]):
- self.conn.executemany(
- "REPLACE INTO peers (id, access_hash, type, username, phone_number)"
- "VALUES (?, ?, ?, ?, ?)",
- peers
- )
+ async def update_peers(self, peers: List[Tuple[int, int, str, List[str], str]]):
+ for peer_data in peers:
+ id, access_hash, type, usernames, phone_number = peer_data
+
+ self.conn.execute(
+ "REPLACE INTO peers (id, access_hash, type, phone_number)"
+ "VALUES (?, ?, ?, ?)",
+ (id, access_hash, type, phone_number)
+ )
+
+ self.conn.execute(
+ "DELETE FROM usernames WHERE id = ?",
+ (id,)
+ )
+
+ self.conn.executemany(
+ "REPLACE INTO usernames (id, username) VALUES (?, ?)",
+ [(id, username) for username in usernames] if usernames else [(id, None)]
+ )
async def get_peer_by_id(self, peer_id: int):
r = self.conn.execute(
@@ -145,8 +164,10 @@ async def get_peer_by_id(self, peer_id: int):
async def get_peer_by_username(self, username: str):
r = self.conn.execute(
- "SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?"
- "ORDER BY last_update_on DESC",
+ "SELECT p.id, p.access_hash, p.type, p.last_update_on FROM peers p "
+ "JOIN usernames u ON p.id = u.id "
+ "WHERE u.username = ? "
+ "ORDER BY p.last_update_on DESC",
(username,)
).fetchone()
diff --git a/pyrogram/storage/storage.py b/pyrogram/storage/storage.py
index 0689b6826..6af890d02 100644
--- a/pyrogram/storage/storage.py
+++ b/pyrogram/storage/storage.py
@@ -33,51 +33,133 @@ def __init__(self, name: str):
self.name = name
async def open(self):
+ """Opens the storage engine."""
raise NotImplementedError
async def save(self):
+ """Saves the current state of the storage engine."""
raise NotImplementedError
async def close(self):
+ """Closes the storage engine."""
raise NotImplementedError
async def delete(self):
+ """Deletes the storage."""
raise NotImplementedError
- async def update_peers(self, peers: List[Tuple[int, int, str, str, str]]):
+ async def update_peers(self, peers: List[Tuple[int, int, str, List[str], str]]):
+ """
+ Update the peers table with the provided information.
+
+ Parameters:
+ peers (``List[Tuple[int, int, str, List[str], str]]``): A list of tuples containing the
+ information of the peers to be updated. Each tuple must contain the following
+ information:
+ - ``int``: The peer id.
+ - ``int``: The peer access hash.
+ - ``str``: The peer type (user, chat or channel).
+ - List of ``str``: The peer username (if any).
+ - ``str``: The peer phone number (if any).
+ """
raise NotImplementedError
async def get_peer_by_id(self, peer_id: int):
+ """Retrieve a peer by its ID.
+
+ Parameters:
+ peer_id (``int``):
+ The ID of the peer to retrieve.
+ """
raise NotImplementedError
async def get_peer_by_username(self, username: str):
+ """Retrieve a peer by its username.
+
+ Parameters:
+ username (``str``):
+ The username of the peer to retrieve.
+ """
raise NotImplementedError
async def get_peer_by_phone_number(self, phone_number: str):
+ """Retrieve a peer by its phone number.
+
+ Parameters:
+ phone_number (``str``):
+ The phone number of the peer to retrieve.
+ """
raise NotImplementedError
async def dc_id(self, value: int = object):
+ """Get or set the DC ID of the current session.
+
+ Parameters:
+ value (``int``, *optional*):
+ The DC ID to set.
+ """
raise NotImplementedError
async def api_id(self, value: int = object):
+ """Get or set the API ID of the current session.
+
+ Parameters:
+ value (``int``, *optional*):
+ The API ID to set.
+ """
raise NotImplementedError
async def test_mode(self, value: bool = object):
+ """Get or set the test mode of the current session.
+
+ Parameters:
+ value (``bool``, *optional*):
+ The test mode to set.
+ """
raise NotImplementedError
async def auth_key(self, value: bytes = object):
+ """Get or set the authorization key of the current session.
+
+ Parameters:
+ value (``bytes``, *optional*):
+ The authorization key to set.
+ """
raise NotImplementedError
async def date(self, value: int = object):
+ """Get or set the date of the current session.
+
+ Parameters:
+ value (``int``, *optional*):
+ The date to set.
+ """
raise NotImplementedError
async def user_id(self, value: int = object):
+ """Get or set the user ID of the current session.
+
+ Parameters:
+ value (``int``, *optional*):
+ The user ID to set.
+ """
raise NotImplementedError
async def is_bot(self, value: bool = object):
+ """Get or set the bot flag of the current session.
+
+ Parameters:
+ value (``bool``, *optional*):
+ The bot flag to set.
+ """
raise NotImplementedError
async def export_session_string(self):
+ """Exports the session string for the current session.
+
+ Returns:
+ ``str``: The session string for the current session.
+ """
packed = struct.pack(
self.SESSION_STRING_FORMAT,
await self.dc_id(),
diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py
index 6f05a3b48..a9dc8ea6b 100644
--- a/pyrogram/types/bots_and_keyboards/__init__.py
+++ b/pyrogram/types/bots_and_keyboards/__init__.py
@@ -39,6 +39,10 @@
from .menu_button_web_app import MenuButtonWebApp
from .reply_keyboard_markup import ReplyKeyboardMarkup
from .reply_keyboard_remove import ReplyKeyboardRemove
+from .request_channel_info import RequestChannelInfo
+from .request_chat_info import RequestChatInfo
+from .request_user_info import RequestUserInfo
+from .request_poll_info import RequestPollInfo
from .sent_web_app_message import SentWebAppMessage
from .web_app_info import WebAppInfo
@@ -52,6 +56,10 @@
"KeyboardButton",
"ReplyKeyboardMarkup",
"ReplyKeyboardRemove",
+ "RequestChannelInfo",
+ "RequestChatInfo",
+ "RequestUserInfo",
+ "RequestPollInfo",
"LoginUrl",
"BotCommand",
"BotCommandScope",
diff --git a/pyrogram/types/bots_and_keyboards/request_channel_info.py b/pyrogram/types/bots_and_keyboards/request_channel_info.py
new file mode 100644
index 000000000..e639c4a26
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/request_channel_info.py
@@ -0,0 +1,61 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 import types
+
+from ..object import Object
+
+
+class RequestChannelInfo(Object):
+ """Contains information about a channel peer type.
+
+ Parameters:
+ button_id (``int``):
+ Identifier of button.
+
+ is_creator (``bool``, *optional*):
+ If True, returns the list of chats owned by the user.
+
+ has_username (``bool``, *optional*):
+ If True, returns the list of chats with a username.
+ If False, returns the list of chats without a username.
+ If not specified, no additional restrictions are applied.
+ Defaults to None.
+
+ user_privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*):
+ Privileged actions that an user administrator is able to take.
+
+ bot_privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*):
+ Privileged actions that an bot administrator is able to take.
+ """
+
+ def __init__(
+ self, *,
+ button_id: int,
+ is_creator: bool = None,
+ has_username: bool = None,
+ user_privileges: "types.ChatPrivileges" = None,
+ bot_privileges: "types.ChatPrivileges" = None
+ ):
+ super().__init__()
+
+ self.button_id = button_id
+ self.is_creator = is_creator
+ self.has_username = has_username
+ self.user_privileges = user_privileges
+ self.bot_privileges = bot_privileges
\ No newline at end of file
diff --git a/pyrogram/types/bots_and_keyboards/request_chat_info.py b/pyrogram/types/bots_and_keyboards/request_chat_info.py
new file mode 100644
index 000000000..62f3412f6
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/request_chat_info.py
@@ -0,0 +1,74 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 import types
+
+from ..object import Object
+
+
+class RequestChatInfo(Object):
+ """Contains information about a chat peer type.
+
+ Parameters:
+ button_id (``int``):
+ Identifier of button.
+
+ is_creator (``bool``, *optional*):
+ If True, returns the list of chats owned by the user.
+
+ is_bot_participant (``bool``, *optional*):
+ If True, returns the list of chats with the bot as a member.
+
+ has_username (``bool``, *optional*):
+ If True, returns the list of chats with a username.
+ If False, returns the list of chats without a username.
+ If not specified, no additional restrictions are applied.
+ Defaults to None.
+
+ has_forum (``bool``, *optional*):
+ If True, returns the list of chats with a forum topics enabled.
+ If False, returns the list of chats without a forum topics.
+ If not specified, no additional restrictions are applied.
+ Defaults to None.
+
+ user_privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*):
+ Privileged actions that an user administrator is able to take.
+
+ bot_privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*):
+ Privileged actions that a bot administrator is able to take.
+ """
+
+ def __init__(
+ self, *,
+ button_id: int,
+ is_creator: bool = None,
+ is_bot_participant: bool = None,
+ has_username: bool = None,
+ has_forum: bool = None,
+ user_privileges: "types.ChatPrivileges" = None,
+ bot_privileges: "types.ChatPrivileges" = None
+ ):
+ super().__init__()
+
+ self.button_id = button_id
+ self.is_creator = is_creator
+ self.is_bot_participant = is_bot_participant
+ self.has_username = has_username
+ self.has_forum = has_forum
+ self.user_privileges = user_privileges
+ self.bot_privileges = bot_privileges
\ No newline at end of file
diff --git a/pyrogram/types/user_and_chats/peer_user.py b/pyrogram/types/bots_and_keyboards/request_poll_info.py
similarity index 73%
rename from pyrogram/types/user_and_chats/peer_user.py
rename to pyrogram/types/bots_and_keyboards/request_poll_info.py
index 2728e7336..e800ac2dd 100644
--- a/pyrogram/types/user_and_chats/peer_user.py
+++ b/pyrogram/types/bots_and_keyboards/request_poll_info.py
@@ -16,29 +16,21 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from pyrogram import raw
from ..object import Object
-class PeerUser(Object):
- """A PeerUser.
-
+class RequestPollInfo(Object):
+ """Contains information about a poll type.
Parameters:
- user_id (``int``):
- Id of the user.
+ is_quiz (``bool``):
+ If True, the requested poll will be sent as quiz.
"""
def __init__(
self, *,
- user_id: int
+ is_quiz: bool = None
):
super().__init__()
- self.user_id = user_id
-
- @staticmethod
- def _parse(action: "raw.types.PeerUser") -> "PeerUser":
- return PeerUser(
- user_id=getattr(action, "user_id", None)
- )
+ self.is_quiz = is_quiz
\ No newline at end of file
diff --git a/pyrogram/types/bots_and_keyboards/request_user_info.py b/pyrogram/types/bots_and_keyboards/request_user_info.py
new file mode 100644
index 000000000..99a46f5a2
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/request_user_info.py
@@ -0,0 +1,58 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 ..object import Object
+
+
+class RequestUserInfo(Object):
+ """Contains information about a user peer type.
+
+ Parameters:
+ button_id (``int``):
+ Identifier of button.
+
+ is_bot (``bool``, *optional*):
+ If True, returns bots.
+ If False to returns regular users.
+ If not specified, no additional restrictions are applied.
+ Defaults to None.
+
+ is_premium (``bool``, *optional*):
+ If True, returns premium users.
+ If False to request non-premium users.
+ If not specified, no additional restrictions are applied.
+ Defaults to None.
+
+ max_quantity(``int``, *optional*):
+ The maximum number of users to be selected; 1-10. Defaults to 1.
+ Defaults to None (One peer only).
+ """
+
+ def __init__(
+ self, *,
+ button_id: int,
+ is_bot: bool = None,
+ is_premium: bool = None,
+ max_quantity: int = None,
+ ):
+ super().__init__()
+
+ self.button_id = button_id
+ self.is_bot = is_bot
+ self.is_premium = is_premium
+ self.max_quantity = max_quantity or 1
\ No newline at end of file
diff --git a/pyrogram/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py
index ebba631e8..0b908cc40 100644
--- a/pyrogram/types/messages_and_media/__init__.py
+++ b/pyrogram/types/messages_and_media/__init__.py
@@ -18,6 +18,7 @@
from .animation import Animation
from .audio import Audio
+from .boosts_status import BoostsStatus
from .contact import Contact
from .dice import Dice
from .document import Document
@@ -29,6 +30,9 @@
from .general_forum_topic_hidden import GeneralTopicHidden
from .general_forum_topic_unhidden import GeneralTopicUnhidden
from .game import Game
+from .gift_code import GiftCode
+from .giveaway import Giveaway
+from .giveaway_result import GiveawayResult
from .location import Location
from .message import Message
from .message_entity import MessageEntity
@@ -39,9 +43,6 @@
from .sticker import Sticker
from .stripped_thumbnail import StrippedThumbnail
from .story import Story
-from .story_deleted import StoryDeleted
-from .story_skipped import StorySkipped
-from .story_views import StoryViews
from .thumbnail import Thumbnail
from .venue import Venue
from .video import Video
@@ -50,14 +51,13 @@
from .web_app_data import WebAppData
from .web_page import WebPage
from .message_reactions import MessageReactions
-from .message_story import MessageStory
from .my_boost import MyBoost
__all__ = [
- "Animation", "Audio", "Contact", "Document", "ForumTopic", "ForumTopicCreated",
+ "Animation", "Audio", "BoostsStatus", "Contact", "Document", "ForumTopic", "ForumTopicCreated",
"ForumTopicClosed", "ForumTopicReopened", "ForumTopicEdited", "GeneralTopicHidden",
- "GeneralTopicUnhidden", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail",
- "StrippedThumbnail", "Story", "StoryDeleted", "StorySkipped", "StoryViews", "Poll", "PollOption", "Sticker",
- "Venue", "Video", "VideoNote", "Voice", "WebPage", "Dice", "Reaction", "WebAppData",
- "MessageReactions", "MessageStory", "MyBoost"
-]
+ "GeneralTopicUnhidden", "Game", "GiftCode", "Giveaway", "GiveawayResult", "Location",
+ "Message", "MessageEntity", "Photo", "Thumbnail", "StrippedThumbnail", "Story", "Poll",
+ "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage", "Dice", "Reaction",
+ "WebAppData", "MessageReactions", "MyBoost"
+]
\ No newline at end of file
diff --git a/pyrogram/types/messages_and_media/boosts_status.py b/pyrogram/types/messages_and_media/boosts_status.py
new file mode 100644
index 000000000..f2e6ac43c
--- /dev/null
+++ b/pyrogram/types/messages_and_media/boosts_status.py
@@ -0,0 +1,88 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import List
+
+from pyrogram import raw, types
+from ..object import Object
+
+
+class BoostsStatus(Object):
+ """Contains information about boost.
+
+ Parameters:
+ level (``int``):
+ Level of channel.
+
+ current_level_boosts (``int``):
+ Number of boosts required for the current level.
+
+ boosts (``int``):
+ Total number of boosts.
+
+ boost_url (``str``):
+ Link that can be used to give a boost to a channel.
+
+ my_boost (``bool``, *optional*):
+ True, if you boost this channel.
+
+ gift_boosts (``int``, *optional*):
+ N/A
+
+ next_level_boosts (``int``, *optional*):
+ Number of boosts at which the next level will be reached.
+
+ my_boost_slots (List of ``int``, *optional*):
+ Boost slots that are given to the channel.
+ """
+
+ def __init__(
+ self,
+ *,
+ level: int,
+ current_level_boosts: int,
+ boosts: int,
+ boost_url: str,
+ my_boost: bool = None,
+ gift_boosts: int = None,
+ next_level_boosts: int = None,
+ my_boost_slots: List[int] = None
+ ):
+ super().__init__()
+
+ self.level = level
+ self.current_level_boosts = current_level_boosts
+ self.boosts = boosts
+ self.boost_url = boost_url
+ self.my_boost = my_boost
+ self.gift_boosts = gift_boosts
+ self.next_level_boosts = next_level_boosts
+ self.my_boost_slots = my_boost_slots
+
+ @staticmethod
+ def _parse(boosts_status: "raw.types.premium.BoostsStatus") -> "BoostsStatus":
+ return BoostsStatus(
+ level=boosts_status.level,
+ current_level_boosts=boosts_status.current_level_boosts,
+ boosts=boosts_status.boosts,
+ boost_url=boosts_status.boost_url,
+ my_boost=getattr(boosts_status, "my_boost", None),
+ gift_boosts=getattr(boosts_status, "gift_boosts", None),
+ next_level_boosts=getattr(boosts_status, "next_level_boosts", None),
+ my_boost_slots=types.List(boosts_status.my_boost_slots) or None,
+ )
diff --git a/pyrogram/types/messages_and_media/forum_topic.py b/pyrogram/types/messages_and_media/forum_topic.py
index 9d5a80ee6..a2525c697 100644
--- a/pyrogram/types/messages_and_media/forum_topic.py
+++ b/pyrogram/types/messages_and_media/forum_topic.py
@@ -16,135 +16,126 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from pyrogram import raw, types
-from typing import Union
+import pyrogram
+from pyrogram import types, raw, utils
from ..object import Object
class ForumTopic(Object):
- # todo
- # notify_settings: `~pyrogram.types.PeerNotifySettings`
- # draft: `~pyrogram.types.DraftMessage`
"""A forum topic.
Parameters:
- id (``Integer``):
- Id of the topic
+ id (``int``):
+ Unique topic identifier inside this chat.
- date (``Integer``):
- Date topic created
+ title (``str``):
+ The topic title.
- title (``String``):
- Name of the topic
+ date (``int``, *optional*):
+ Date when the topic was created.
- icon_color (``Integer``):
- Color of the topic icon in RGB format
+ icon_color (``str``, *optional*):
+ Color of the topic icon in HEX format
- top_message (``Integer``):
- N/A
-
- read_inbox_max_id (``Integer``):
- N/A
+ icon_emoji_id (``int``, *optional*):
+ Unique identifier of the custom emoji shown as the topic icon
- read_outbox_max_id (``Integer``):
- N/A
+ creator (:obj:`~pyrogram.types.Chat`, *optional*):
+ Topic creator.
- unread_count (``Integer``):
- N/A
+ top_message (:obj:`~pyrogram.types.Message`, *optional*):
+ The last message sent in the topic at this time.
- unread_mentions_count (``Integer``):
- N/A
+ unread_count (``int``, *optional*):
+ Amount of unread messages in this topic.
- unread_reactions_count (``Integer``):
- N/A
+ unread_mentions_count (``int``, *optional*):
+ Amount of unread messages containing a mention in this topic.
- from_id (:obj:`~pyrogram.types.PeerChannel` | :obj:`~pyrogram.types.PeerUser`):
- Topic creator.
+ unread_reactions_count (``int``, *optional*):
+ Amount of unread messages containing a reaction in this topic.
- my (``Boolean``, *optional*):
- N/A
+ is_my (``bool``, *optional*):
+ True, if you are creator of topic.
- closed (``Boolean``, *optional*):
- N/A
+ is_closed (``bool``, *optional*):
+ True, if the topic is closed.
- pinned (``Boolean``, *optional*):
- N/A
+ is_pinned (``bool``, *optional*):
+ True, if the topic is pinned.
- short (``Boolean``, *optional*):
- N/A
+ is_short (``bool``, *optional*):
+ True, if the topic is short.
- icon_emoji_id (``Integer``, *optional*):
- Unique identifier of the custom emoji shown as the topic icon
+ is_hidden (``bool``, *optional*):
+ True, if the topic is hidden.
"""
def __init__(
self,
*,
id: int,
- date: int,
- title: str,
- icon_color: int,
- top_message: int,
- read_inbox_max_id: int,
- read_outbox_max_id: int,
- unread_count: int,
- unread_mentions_count: int,
- unread_reactions_count: int,
- from_id: Union["types.PeerChannel", "types.PeerUser"],
- # notify_settings: "types.PeerNotifySettings", //todo
- my: bool = None,
- closed: bool = None,
- pinned: bool = None,
- short: bool = None,
+ title: str = None,
+ date: int = None,
+ icon_color: str = None,
icon_emoji_id: int = None,
- # draft: "types.DraftMessage" = None //todo
+ creator: "types.Chat" = None,
+ top_message: "types.Message" = None,
+ unread_count: int = None,
+ unread_mentions_count: int = None,
+ unread_reactions_count: int = None,
+ is_my: bool = None,
+ is_closed: bool = None,
+ is_pinned: bool = None,
+ is_short: bool = None,
+ is_hidden: bool = None
):
super().__init__()
self.id = id
- self.date = date
self.title = title
+ self.date = date
self.icon_color = icon_color
+ self.icon_emoji_id = icon_emoji_id
+ self.creator = creator
self.top_message = top_message
- self.read_inbox_max_id = read_inbox_max_id
- self.read_outbox_max_id = read_outbox_max_id
self.unread_count = unread_count
self.unread_mentions_count = unread_mentions_count
self.unread_reactions_count = unread_reactions_count
- self.from_id = from_id
- # self.notify_settings = notify_settings //todo
- self.my = my
- self.closed = closed
- self.pinned = pinned
- self.short = short
- self.icon_emoji_id = icon_emoji_id
- # self.draft = draft //todo
+ self.is_my = is_my
+ self.is_closed = is_closed
+ self.is_pinned = is_pinned
+ self.is_short = is_short
+ self.is_hidden = is_hidden
@staticmethod
- def _parse(forum_topic: "raw.types.forum_topic") -> "ForumTopic":
- from_id = forum_topic.from_id
- if isinstance(from_id, raw.types.PeerChannel):
- peer = types.PeerChannel._parse(from_id)
- if isinstance(from_id, raw.types.PeerUser):
- peer = types.PeerUser._parse(from_id)
+ def _parse(client: "pyrogram.Client", forum_topic: "raw.types.ForumTopic", messages: dict = {}, users: dict = {}, chats: dict = {}) -> "ForumTopic":
+ creator = None
+
+ peer = getattr(forum_topic, "from_id", None)
+
+ if peer:
+ peer_id = utils.get_raw_peer_id(peer)
+
+ if isinstance(peer, raw.types.PeerUser):
+ creator = types.Chat._parse_user_chat(client, users[peer_id])
+ else:
+ creator = types.Chat._parse_channel_chat(client, chats[peer_id])
return ForumTopic(
- id=getattr(forum_topic, "id", None),
- date=getattr(forum_topic, "date", None),
- title=getattr(forum_topic, "title", None),
- icon_color=getattr(forum_topic, "icon_color", None),
- top_message=getattr(forum_topic, "top_message", None),
- read_inbox_max_id=getattr(forum_topic, "read_inbox_max_id", None),
- read_outbox_max_id=getattr(forum_topic, "read_outbox_max_id", None),
+ id=forum_topic.id,
+ title=forum_topic.title,
+ date=utils.timestamp_to_datetime(forum_topic.date),
+ icon_color=format(forum_topic.icon_color, "x") if getattr(forum_topic, "icon_color", None) else None,
+ icon_emoji_id=getattr(forum_topic, "icon_emoji_id", None),
+ creator=creator,
+ top_message=messages.get(getattr(forum_topic, "top_message", None)),
unread_count=getattr(forum_topic, "unread_count", None),
unread_mentions_count=getattr(forum_topic, "unread_mentions_count", None),
unread_reactions_count=getattr(forum_topic, "unread_reactions_count", None),
- from_id=peer,
- # notify_settings=None, //todo
- my=getattr(forum_topic, "my", None),
- closed=getattr(forum_topic, "closed", None),
- pinned=getattr(forum_topic, "pinned", None),
- short=getattr(forum_topic, "short", None),
- icon_emoji_id=getattr(forum_topic, "icon_emoji_id", None),
- # draft=None //todo
+ is_my=getattr(forum_topic, "my", None),
+ is_closed=getattr(forum_topic, "closed", None),
+ is_pinned=getattr(forum_topic, "pinned", None),
+ is_short=getattr(forum_topic, "short", None),
+ is_hidden=getattr(forum_topic, "hidden", None),
)
diff --git a/pyrogram/types/messages_and_media/gift_code.py b/pyrogram/types/messages_and_media/gift_code.py
new file mode 100644
index 000000000..70b5571dc
--- /dev/null
+++ b/pyrogram/types/messages_and_media/gift_code.py
@@ -0,0 +1,79 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 import raw, types, utils
+from ..object import Object
+
+
+class GiftCode(Object):
+ """Contains gift code data.
+
+ Parameters:
+ via_giveaway (``bool``):
+ True if the gift code is received via giveaway.
+
+ unclaimed (``bool``):
+ True if the gift code is unclaimed.
+
+ boost_peer (:obj:`~pyrogram.types.Chat`):
+ The channel where the gift code was won.
+
+ months (``int``):
+ Number of months of subscription.
+
+ slug (``str``):
+ Identifier of gift code.
+ You can combine it with `t.me/giftcode/{slug}`
+ to get link for this gift.
+
+ link (``str``, *property*):
+ Generate a link to this gift code.
+ """
+
+ def __init__(
+ self,
+ *,
+ via_giveaway: bool,
+ unclaimed: bool,
+ boost_peer,
+ months: int,
+ slug: str
+ ):
+ super().__init__()
+
+ self.via_giveaway = via_giveaway
+ self.unclaimed = unclaimed
+ self.boost_peer = boost_peer
+ self.months = months
+ self.slug = slug
+
+ @staticmethod
+ def _parse(client, giftcode: "raw.types.MessageActionGiftCode", chats):
+ peer = chats.get(utils.get_raw_peer_id(giftcode.boost_peer))
+
+ return GiftCode(
+ via_giveaway=giftcode.via_giveaway,
+ unclaimed=giftcode.unclaimed,
+ boost_peer=types.Chat._parse_chat(client, peer) if peer else None,
+ months=giftcode.months,
+ slug=giftcode.slug
+ )
+
+ @property
+ def link(self) -> str:
+ return f"https://t.me/giftcode/{self.slug}"
\ No newline at end of file
diff --git a/pyrogram/types/messages_and_media/giveaway.py b/pyrogram/types/messages_and_media/giveaway.py
new file mode 100644
index 000000000..04c18c00a
--- /dev/null
+++ b/pyrogram/types/messages_and_media/giveaway.py
@@ -0,0 +1,100 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 datetime import datetime
+from typing import List
+
+import pyrogram
+from pyrogram import raw, utils
+from pyrogram import types
+from ..object import Object
+
+
+class Giveaway(Object):
+ """An giveaway.
+
+ Parameters:
+ chats (List of :obj:`~pyrogram.types.Chat`):
+ Get the list of channels you need to subscribe to.
+
+ quantity (``int``):
+ Number of subscriptions.
+
+ months (``int``):
+ Number of months for which a subscription is given.
+
+ until_date (:py:obj:`~datetime.datetime`):
+ Date when the giveaway will end.
+
+ description (``str``, *optional*):
+ Prize description.
+
+ only_new_subscribers (``bool``, *optional*):
+ True, if this giveaway is for new subscribers only.
+
+ only_for_countries (List of ``str`` , *optional*):
+ A list of two-letter ISO 3166-1 alpha-2 country codes indicating the countries
+ from which eligible users for the giveaway must come.
+ If None, then all users can participate in the giveaway.
+ Users with a phone number that was bought on Fragment can always participate in giveaways.
+
+ winners_are_visible (``bool``, *optional*):
+ True, if this giveaway winners is visible.
+ """
+
+ def __init__(
+ self,
+ *,
+ client: "pyrogram.Client" = None,
+ chats: List["types.Chat"] = None,
+ quantity: int = None,
+ months: int = None,
+ until_date: datetime = None,
+ description: str = None,
+ only_new_subscribers: bool = None,
+ only_for_countries: List[str] = None,
+ winners_are_visible: bool = None
+ ):
+ super().__init__(client)
+
+ self.chats = chats
+ self.quantity = quantity
+ self.months = months
+ self.until_date = until_date
+ self.description = description
+ self.only_new_subscribers = only_new_subscribers
+ self.only_for_countries = only_for_countries
+ self.winners_are_visible = winners_are_visible
+
+ @staticmethod
+ def _parse(
+ client,
+ giveaway: "raw.types.MessageMediaGiveaway",
+ chats: dict
+ ) -> "Giveaway":
+ return Giveaway(
+ chats=types.List(types.Chat._parse_channel_chat(client, chats.get(i)) for i in giveaway.channels),
+ quantity=giveaway.quantity,
+ months=giveaway.months,
+ until_date=utils.timestamp_to_datetime(giveaway.until_date),
+ description=getattr(giveaway, "prize_description", None) or None,
+ only_new_subscribers=getattr(giveaway, "only_new_subscribers", None),
+ only_for_countries=types.List(getattr(giveaway, "countries_iso2", [])) or None,
+ winners_are_visible=getattr(giveaway, "winners_are_visible", None),
+ client=client
+ )
\ No newline at end of file
diff --git a/pyrogram/types/messages_and_media/giveaway_result.py b/pyrogram/types/messages_and_media/giveaway_result.py
new file mode 100644
index 000000000..842ad1470
--- /dev/null
+++ b/pyrogram/types/messages_and_media/giveaway_result.py
@@ -0,0 +1,134 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 datetime import datetime
+from typing import List
+
+import pyrogram
+from pyrogram import errors, raw, utils
+from pyrogram import types
+from ..object import Object
+
+
+class GiveawayResult(Object):
+ """An giveaway result.
+
+ Parameters:
+ chat (List of :obj:`~pyrogram.types.Chat`):
+ Channel which host the giveaway.
+
+ quantity (``int``):
+ Total number of subscriptions in this giveaway.
+
+ winners_count (``int``):
+ Number of winners who claimed their gift.
+
+ unclaimed_count (``int``):
+ Unclaimed giveaway subscriptions count.
+
+ winners (List of :obj:`~pyrogram.types.User`):
+ A list of giveaway winners.
+
+ months (``int``):
+ Number of months for which a subscription is given.
+
+ until_date (:py:obj:`~datetime.datetime`):
+ Date when the giveaway will end.
+
+ launch_message_id (``int``):
+ Identifier of the original message with the giveaway.
+
+ launch_message (:obj:`~pyrogram.types.Message`, *optional*):
+ Returns the original giveaway start message.
+ If the channel is private, returns None
+
+ description (``str``, *optional*):
+ Prize description.
+
+ only_new_subscribers (``bool``, *optional*):
+ True, if this giveaway is for new subscribers only.
+
+ is_refunded (``bool``, *optional*):
+ True, if this giveaway was refunded.
+ """
+
+ def __init__(
+ self,
+ *,
+ client: "pyrogram.Client" = None,
+ chat: "types.Chat",
+ quantity: int,
+ winners_count: int,
+ unclaimed_count: int,
+ winners: List["types.User"],
+ months: int,
+ until_date: datetime,
+ launch_message_id: int,
+ launch_message: "types.Message" = None,
+ description: str = None,
+ only_new_subscribers: bool = None,
+ is_refunded: bool = None
+ ):
+ super().__init__(client)
+
+ self.chat = chat
+ self.quantity = quantity
+ self.winners_count = winners_count
+ self.unclaimed_count = unclaimed_count
+ self.winners = winners
+ self.months = months
+ self.until_date = until_date
+ self.launch_message_id = launch_message_id
+ self.launch_message = launch_message
+ self.description = description
+ self.only_new_subscribers = only_new_subscribers
+ self.is_refunded = is_refunded
+
+ @staticmethod
+ async def _parse(
+ client,
+ giveaway_result: "raw.types.MessageMediaGiveawayResults",
+ users: dict,
+ chats: dict
+ ) -> "GiveawayResult":
+ launch_message = None
+
+ try:
+ launch_message = await client.get_messages(
+ utils.get_channel_id(giveaway_result.channel_id),
+ giveaway_result.launch_msg_id,
+ replies=0
+ )
+ except (errors.ChannelPrivate, errors.ChannelInvalid):
+ pass
+
+ return GiveawayResult(
+ chat=types.Chat._parse_channel_chat(client, chats[giveaway_result.channel_id]),
+ quantity=giveaway_result.winners_count + giveaway_result.unclaimed_count,
+ winners_count=giveaway_result.winners_count,
+ unclaimed_count=giveaway_result.unclaimed_count,
+ winners=types.List(types.User._parse(client, users.get(i)) for i in giveaway_result.winners) or None,
+ months=giveaway_result.months,
+ until_date=utils.timestamp_to_datetime(giveaway_result.until_date),
+ launch_message_id=giveaway_result.launch_msg_id,
+ only_new_subscribers=getattr(giveaway_result, "only_new_subscribers", None),
+ is_refunded=getattr(giveaway_result, "refunded", None),
+ launch_message=launch_message,
+ description=getattr(giveaway_result, "prize_description", None) or None,
+ client=client
+ )
\ No newline at end of file
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 8392585e8..0591f5ba0 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -25,7 +25,7 @@
from pyrogram import raw, enums
from pyrogram import types
from pyrogram import utils
-from pyrogram.errors import MessageIdsEmpty, PeerIdInvalid
+from pyrogram.errors import MessageIdsEmpty, PeerIdInvalid, ChannelPrivate, BotMethodInvalid
from pyrogram.parser import utils as parser_utils, Parser
from ..object import Object
from ..update import Update
@@ -78,7 +78,7 @@ class Message(Object, Update):
chat (:obj:`~pyrogram.types.Chat`, *optional*):
Conversation the message belongs to.
- topics (:obj:`~pyrogram.types.ForumTopic`, *optional*):
+ topic (:obj:`~pyrogram.types.ForumTopic`, *optional*):
Topic the message belongs to.
forward_from (:obj:`~pyrogram.types.User`, *optional*):
@@ -99,12 +99,9 @@ class Message(Object, Update):
forward_date (:py:obj:`~datetime.datetime`, *optional*):
For forwarded messages, date the original message was sent.
- is_topic_message (``bool``, *optional*):
- True, if the message is sent to a forum topic
-
message_thread_id (``int``, *optional*):
Unique identifier of a message thread to which the message belongs.
- for supergroups only
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
The id of the message which this message directly replied to.
@@ -143,12 +140,17 @@ class Message(Object, Update):
You can use ``media = getattr(message, message.media.value)`` to access the media message.
invert_media (``bool``, *optional*):
- Invert media.
+ If True, link preview will be shown above the message text.
+ Otherwise, the link preview will be shown below the message text.
edit_date (:py:obj:`~datetime.datetime`, *optional*):
Date the message was last edited.
- media_group_id (``str``, *optional*):
+ edit_hidden (``bool``, *optional*):
+ The message shown as not modified.
+ A message can be not modified in case it has received a reaction.
+
+ media_group_id (``int``, *optional*):
The unique identifier of a media message group this message belongs to.
author_signature (``str``, *optional*):
@@ -201,7 +203,10 @@ class Message(Object, Update):
game (:obj:`~pyrogram.types.Game`, *optional*):
Message is a game, information about the game.
- story (:obj:`~pyrogram.types.MessageStory`, *optional*):
+ giveaway (:obj:`~pyrogram.types.Giveaway`, *optional*):
+ Message is a giveaway, information about the giveaway.
+
+ story (:obj:`~pyrogram.types.Story`, *optional*):
Message is a story, information about the story.
video (:obj:`~pyrogram.types.Video`, *optional*):
@@ -349,6 +354,12 @@ class Message(Object, Update):
web_app_data (:obj:`~pyrogram.types.WebAppData`, *optional*):
Service message: web app data sent to the bot.
+ gift_code (:obj:`~pyrogram.types.GiftCode`, *optional*):
+ Service message: gift code information.
+
+ requested_chats (List of :obj:`~pyrogram.types.Chat`, *optional*):
+ Service message: requested chats information.
+
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
Additional interface options. An object for an inline keyboard, custom reply keyboard,
instructions to remove reply keyboard or to force a reply from the user.
@@ -371,14 +382,13 @@ def __init__(
sender_chat: "types.Chat" = None,
date: datetime = None,
chat: "types.Chat" = None,
- topics: "types.ForumTopic" = None,
+ topic: "types.ForumTopic" = None,
forward_from: "types.User" = None,
forward_sender_name: str = None,
forward_from_chat: "types.Chat" = None,
forward_from_message_id: int = None,
forward_signature: str = None,
forward_date: datetime = None,
- is_topic_message: bool = None,
message_thread_id: int = None,
reply_to_message_id: int = None,
reply_to_story_id: int = None,
@@ -394,7 +404,8 @@ def __init__(
media: "enums.MessageMediaType" = None,
invert_media: bool = None,
edit_date: datetime = None,
- media_group_id: str = None,
+ edit_hidden: bool = None,
+ media_group_id: int = None,
author_signature: str = None,
has_protected_content: bool = None,
has_media_spoiler: bool = None,
@@ -409,7 +420,9 @@ def __init__(
sticker: "types.Sticker" = None,
animation: "types.Animation" = None,
game: "types.Game" = None,
- story: "types.MessageStory" = None,
+ giveaway: "types.Giveaway" = None,
+ giveaway_result: "types.GiveawayResult" = None,
+ story: "types.Story" = None,
video: "types.Video" = None,
voice: "types.Voice" = None,
video_note: "types.VideoNote" = None,
@@ -450,6 +463,9 @@ def __init__(
video_chat_ended: "types.VideoChatEnded" = None,
video_chat_members_invited: "types.VideoChatMembersInvited" = None,
web_app_data: "types.WebAppData" = None,
+ gift_code: "types.GiftCode" = None,
+ requested_chats: List["types.Chat"] = None,
+ giveaway_launched: bool = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
"types.ReplyKeyboardMarkup",
@@ -465,14 +481,13 @@ def __init__(
self.sender_chat = sender_chat
self.date = date
self.chat = chat
- self.topics = topics
+ self.topic = topic
self.forward_from = forward_from
self.forward_sender_name = forward_sender_name
self.forward_from_chat = forward_from_chat
self.forward_from_message_id = forward_from_message_id
self.forward_signature = forward_signature
self.forward_date = forward_date
- self.is_topic_message = is_topic_message
self.message_thread_id = message_thread_id
self.reply_to_message_id = reply_to_message_id
self.reply_to_story_id = reply_to_story_id
@@ -488,6 +503,7 @@ def __init__(
self.media = media
self.invert_media = invert_media
self.edit_date = edit_date
+ self.edit_hidden = edit_hidden
self.media_group_id = media_group_id
self.author_signature = author_signature
self.has_protected_content = has_protected_content
@@ -503,6 +519,8 @@ def __init__(
self.sticker = sticker
self.animation = animation
self.game = game
+ self.giveaway = giveaway
+ self.giveaway_result = giveaway_result
self.story = story
self.video = video
self.voice = voice
@@ -545,6 +563,9 @@ def __init__(
self.video_chat_ended = video_chat_ended
self.video_chat_members_invited = video_chat_members_invited
self.web_app_data = web_app_data
+ self.gift_code = gift_code
+ self.requested_chats = requested_chats
+ self.giveaway_launched = giveaway_launched
self.reactions = reactions
@staticmethod
@@ -584,6 +605,7 @@ async def _parse(
message_thread_id = None
action = message.action
+ text = None
new_chat_members = None
left_chat_member = None
new_chat_title = None
@@ -593,7 +615,6 @@ async def _parse(
group_chat_created = None
channel_chat_created = None
new_chat_photo = None
- is_topic_message = None
forum_topic_created = None
forum_topic_closed = None
forum_topic_reopened = None
@@ -605,6 +626,9 @@ async def _parse(
video_chat_ended = None
video_chat_members_invited = None
web_app_data = None
+ gift_code = None
+ giveaway_launched = None
+ requested_chats = None
service_type = None
@@ -638,6 +662,9 @@ async def _parse(
elif isinstance(action, raw.types.MessageActionChatEditPhoto):
new_chat_photo = types.Photo._parse(client, action.photo)
service_type = enums.MessageServiceType.NEW_CHAT_PHOTO
+ elif isinstance(action, raw.types.MessageActionCustomAction):
+ text = message.action.message
+ service_type = enums.MessageServiceType.CUSTOM_ACTION
elif isinstance(action, raw.types.MessageActionTopicCreate):
forum_topic_created = types.ForumTopicCreated._parse(message)
service_type = enums.MessageServiceType.FORUM_TOPIC_CREATED
@@ -674,6 +701,37 @@ async def _parse(
elif isinstance(action, raw.types.MessageActionWebViewDataSentMe):
web_app_data = types.WebAppData._parse(action)
service_type = enums.MessageServiceType.WEB_APP_DATA
+ elif isinstance(action, raw.types.MessageActionGiveawayLaunch):
+ giveaway_launched = True
+ service_type = enums.MessageServiceType.GIVEAWAY_LAUNCH
+ elif isinstance(action, raw.types.MessageActionGiftCode):
+ gift_code = types.GiftCode._parse(client, action, chats)
+ service_type = enums.MessageServiceType.GIFT_CODE
+ elif isinstance(action, raw.types.MessageActionRequestedPeer):
+ _requested_chats = []
+
+ for requested_peer in action.peers:
+ chat_id = utils.get_peer_id(requested_peer)
+ peer_type = utils.get_peer_type(chat_id)
+
+ if peer_type == "user":
+ chat_type = enums.ChatType.PRIVATE
+ elif peer_type == "chat":
+ chat_type = enums.ChatType.GROUP
+ else:
+ chat_type = enums.ChatType.CHANNEL
+
+ _requested_chats.append(
+ types.Chat(
+ id=chat_id,
+ type=chat_type,
+ client=client
+ )
+ )
+
+ requested_chats = types.List(_requested_chats) or None
+
+ service_type = enums.MessageServiceType.REQUESTED_CHAT
from_user = types.User._parse(client, users.get(user_id, None))
sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None
@@ -683,10 +741,10 @@ async def _parse(
message_thread_id=message_thread_id,
date=utils.timestamp_to_datetime(message.date),
chat=types.Chat._parse(client, message, users, chats, is_chat=True),
- topics=None,
from_user=from_user,
sender_chat=sender_chat,
service=service_type,
+ text=text,
new_chat_members=new_chat_members,
left_chat_member=left_chat_member,
new_chat_title=new_chat_title,
@@ -696,7 +754,6 @@ async def _parse(
migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None,
group_chat_created=group_chat_created,
channel_chat_created=channel_chat_created,
- is_topic_message=is_topic_message,
forum_topic_created=forum_topic_created,
forum_topic_closed=forum_topic_closed,
forum_topic_reopened=forum_topic_reopened,
@@ -708,6 +765,9 @@ async def _parse(
video_chat_ended=video_chat_ended,
video_chat_members_invited=video_chat_members_invited,
web_app_data=web_app_data,
+ giveaway_launched=giveaway_launched,
+ gift_code=gift_code,
+ requested_chats=requested_chats,
client=client
# TODO: supergroup_chat_created
)
@@ -741,13 +801,11 @@ async def _parse(
client.message_cache[(parsed_message.chat.id, parsed_message.id)] = parsed_message
- if message.reply_to:
- if message.reply_to.forum_topic:
- if message.reply_to.reply_to_top_id:
- parsed_message.message_thread_id = message.reply_to.reply_to_top_id
- else:
- parsed_message.message_thread_id = message.reply_to.reply_to_msg_id
- parsed_message.is_topic_message = True
+ if message.reply_to and message.reply_to.forum_topic:
+ if message.reply_to.reply_to_top_id:
+ parsed_message.message_thread_id = message.reply_to.reply_to_top_id
+ else:
+ parsed_message.message_thread_id = message.reply_to.reply_to_msg_id
return parsed_message
@@ -762,7 +820,6 @@ async def _parse(
forward_from_message_id = None
forward_signature = None
forward_date = None
- is_topic_message = None
forward_header = message.fwd_from # type: raw.types.MessageFwdHeader
@@ -787,6 +844,8 @@ async def _parse(
contact = None
venue = None
game = None
+ giveaway = None
+ giveaway_result = None
story = None
audio = None
voice = None
@@ -820,8 +879,21 @@ async def _parse(
elif isinstance(media, raw.types.MessageMediaGame):
game = types.Game._parse(client, message)
media_type = enums.MessageMediaType.GAME
+ elif isinstance(media, raw.types.MessageMediaGiveaway):
+ giveaway = types.Giveaway._parse(client, media, chats)
+ media_type = enums.MessageMediaType.GIVEAWAY
+ elif isinstance(media, raw.types.MessageMediaGiveawayResults):
+ giveaway_result = await types.GiveawayResult._parse(client, media, users, chats)
+ media_type = enums.MessageMediaType.GIVEAWAY_RESULT
elif isinstance(media, raw.types.MessageMediaStory):
- story = types.MessageStory._parse(client, media, users, chats)
+ if media.story:
+ story = await types.Story._parse(client, media.story, users, chats, media.peer)
+ else:
+ try:
+ story = await client.get_stories(utils.get_peer_id(media.peer), media.id)
+ except BotMethodInvalid:
+ story = await types.Story._parse(client, media, users, chats, media.peer)
+
media_type = enums.MessageMediaType.STORY
elif isinstance(media, raw.types.MessageMediaDocument):
doc = media.document
@@ -867,7 +939,14 @@ async def _parse(
media_type = enums.MessageMediaType.DOCUMENT
elif isinstance(media, raw.types.MessageMediaWebPage):
if isinstance(media.webpage, raw.types.WebPage):
- web_page = types.WebPage._parse(client, media.webpage)
+ web_page = types.WebPage._parse(
+ client,
+ media.webpage,
+ getattr(media, "force_large_media", None),
+ getattr(media, "force_small_media", None),
+ getattr(media, "manual", None),
+ getattr(media, "safe", None)
+ )
media_type = enums.MessageMediaType.WEB_PAGE
else:
media = None
@@ -904,7 +983,6 @@ async def _parse(
message_thread_id=message_thread_id,
date=utils.timestamp_to_datetime(message.date),
chat=types.Chat._parse(client, message, users, chats, is_chat=True),
- topics=None,
from_user=from_user,
sender_chat=sender_chat,
text=(
@@ -936,13 +1014,13 @@ async def _parse(
forward_from_message_id=forward_from_message_id,
forward_signature=forward_signature,
forward_date=forward_date,
- is_topic_message=is_topic_message,
mentioned=message.mentioned,
scheduled=is_scheduled,
from_scheduled=message.from_scheduled,
media=media_type,
invert_media=getattr(message, "invert_media", None),
edit_date=utils.timestamp_to_datetime(message.edit_date),
+ edit_hidden=message.edit_hide,
media_group_id=message.grouped_id,
photo=photo,
location=location,
@@ -952,6 +1030,8 @@ async def _parse(
voice=voice,
animation=animation,
game=game,
+ giveaway=giveaway,
+ giveaway_result=giveaway_result,
story=story,
video=video,
video_note=video_note,
@@ -980,17 +1060,11 @@ async def _parse(
parsed_message.reply_to_message_id = message.reply_to.reply_to_msg_id
else:
thread_id = message.reply_to.reply_to_msg_id
+
parsed_message.message_thread_id = thread_id
- parsed_message.is_topic_message = True
+
if topics:
- parsed_message.topics = types.ForumTopic._parse(topics[thread_id])
- else:
- try:
- msg = await client.get_messages(parsed_message.chat.id,message.id)
- if getattr(msg, "topics"):
- parsed_message.topics = msg.topics
- except Exception:
- pass
+ parsed_message.topic = types.ForumTopic._parse(client, topics[thread_id], users=users, chats=chats)
else:
if message.reply_to.quote:
quote_entities = [types.MessageEntity._parse(client, entity, users) for entity in message.reply_to.quote_entities]
@@ -1007,42 +1081,35 @@ async def _parse(
if media is None or web_page is not None
else None
)
+
parsed_message.reply_to_message_id = message.reply_to.reply_to_msg_id
parsed_message.reply_to_top_message_id = message.reply_to.reply_to_top_id
else:
parsed_message.reply_to_story_id = message.reply_to.story_id
parsed_message.reply_to_story_user_id = message.reply_to.user_id
- setattr(parsed_message, "is_cross_chat", False)
if replies:
if parsed_message.reply_to_message_id:
+ is_cross_chat = getattr(message.reply_to, "reply_to_peer_id", None) and getattr(message.reply_to.reply_to_peer_id, "channel_id", None)
+
+ if is_cross_chat:
+ key = (utils.get_channel_id(message.reply_to.reply_to_peer_id.channel_id), message.reply_to.reply_to_msg_id)
+ reply_to_params = {"chat_id": key[0], 'message_ids': key[1]}
+ else:
+ key = (parsed_message.chat.id, parsed_message.reply_to_message_id)
+ reply_to_params = {'chat_id': key[0], 'reply_to_message_ids': message.id}
+
try:
- is_cross_chat = \
- message.reply_to and \
- message.reply_to.reply_to_peer_id and \
- hasattr(message.reply_to.reply_to_peer_id, "channel_id") and \
- message.reply_to.reply_to_peer_id.channel_id
- if is_cross_chat:
- key = (utils.get_channel_id(message.reply_to.reply_to_peer_id.channel_id),
- message.reply_to.reply_to_msg_id)
- setattr(parsed_message, "is_cross_chat", True)
- else:
- key = (parsed_message.chat.id, parsed_message.reply_to_message_id)
reply_to_message = client.message_cache[key]
if not reply_to_message:
- if is_cross_chat:
+ try:
reply_to_message = await client.get_messages(
- chat_id=key[0],
- message_ids=key[1],
- replies=replies - 1
- )
- else:
- reply_to_message = await client.get_messages(
- chat_id=key[0],
- reply_to_message_ids=message.id,
- replies=replies - 1
+ replies=replies - 1,
+ **reply_to_params
)
+ except ChannelPrivate:
+ pass
if reply_to_message and not reply_to_message.forum_topic_created:
parsed_message.reply_to_message = reply_to_message
except MessageIdsEmpty:
@@ -1053,11 +1120,20 @@ async def _parse(
parsed_message.reply_to_story_user_id,
parsed_message.reply_to_story_id
)
- except Exception:
+ except BotMethodInvalid:
pass
else:
parsed_message.reply_to_story = reply_to_story
+ if parsed_message.topic is None and parsed_message.chat.is_forum:
+ try:
+ parsed_message.topic = await client.get_forum_topics_by_id(
+ chat_id=parsed_message.chat.id,
+ topic_ids=parsed_message.message_thread_id or 1
+ )
+ except BotMethodInvalid:
+ pass
+
if not parsed_message.poll: # Do not cache poll messages
client.message_cache[(parsed_message.chat.id, parsed_message.id)] = parsed_message
@@ -1119,6 +1195,7 @@ async def reply_text(
disable_web_page_preview: bool = None,
disable_notification: bool = None,
message_thread_id: int = None,
+ invert_media: bool = None,
reply_to_message_id: int = None,
quote_text: str = None,
quote_entities: List["types.MessageEntity"] = None,
@@ -1169,7 +1246,12 @@ async def reply_text(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
+
+ invert_media (``bool``, *optional*):
+ If True, link preview will be shown above the message text.
+ Otherwise, the link preview will be shown below the message text.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -1202,6 +1284,9 @@ async def reply_text(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_message(
chat_id=self.chat.id,
text=text,
@@ -1210,6 +1295,7 @@ async def reply_text(
disable_web_page_preview=disable_web_page_preview,
disable_notification=disable_notification,
message_thread_id=message_thread_id,
+ invert_media=invert_media,
reply_to_message_id=reply_to_message_id,
quote_text=quote_text,
quote_entities=quote_entities,
@@ -1307,7 +1393,8 @@ async def reply_animation(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -1358,6 +1445,9 @@ async def reply_animation(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_animation(
chat_id=self.chat.id,
animation=animation,
@@ -1462,7 +1552,8 @@ async def reply_audio(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -1513,6 +1604,9 @@ async def reply_audio(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_audio(
chat_id=self.chat.id,
audio=audio,
@@ -1593,7 +1687,8 @@ async def reply_cached_media(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -1620,6 +1715,9 @@ async def reply_cached_media(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_cached_media(
chat_id=self.chat.id,
file_id=file_id,
@@ -1731,7 +1829,8 @@ async def reply_contact(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -1762,6 +1861,9 @@ async def reply_contact(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_contact(
chat_id=self.chat.id,
phone_number=phone_number,
@@ -1860,7 +1962,8 @@ async def reply_document(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -1918,6 +2021,9 @@ async def reply_document(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_document(
chat_id=self.chat.id,
document=document,
@@ -1982,7 +2088,8 @@ async def reply_game(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -2003,6 +2110,9 @@ async def reply_game(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_game(
chat_id=self.chat.id,
game_short_name=game_short_name,
@@ -2058,7 +2168,8 @@ async def reply_inline_bot_result(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``bool``, *optional*):
If the message is a reply, ID of the original message.
@@ -2085,6 +2196,9 @@ async def reply_inline_bot_result(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_inline_bot_result(
chat_id=self.chat.id,
query_id=query_id,
@@ -2148,7 +2262,8 @@ async def reply_location(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message
@@ -2175,6 +2290,9 @@ async def reply_location(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_location(
chat_id=self.chat.id,
latitude=latitude,
@@ -2230,7 +2348,8 @@ async def reply_media_group(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -2258,6 +2377,9 @@ async def reply_media_group(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_media_group(
chat_id=self.chat.id,
media=media,
@@ -2343,7 +2465,8 @@ async def reply_photo(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -2394,6 +2517,9 @@ async def reply_photo(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_photo(
chat_id=self.chat.id,
photo=photo,
@@ -2519,7 +2645,8 @@ async def reply_poll(
Protects the contents of the sent message from forwarding and saving.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -2553,6 +2680,9 @@ async def reply_poll(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_poll(
chat_id=self.chat.id,
question=question,
@@ -2630,7 +2760,8 @@ async def reply_sticker(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -2685,6 +2816,9 @@ async def reply_sticker(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_sticker(
chat_id=self.chat.id,
sticker=sticker,
@@ -2770,7 +2904,8 @@ async def reply_venue(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message
@@ -2801,6 +2936,9 @@ async def reply_venue(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_venue(
chat_id=self.chat.id,
latitude=latitude,
@@ -2915,7 +3053,8 @@ async def reply_video(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@@ -2966,6 +3105,9 @@ async def reply_video(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_video(
chat_id=self.chat.id,
video=video,
@@ -3056,7 +3198,8 @@ async def reply_video_note(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message
@@ -3111,6 +3254,9 @@ async def reply_video_note(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_video_note(
chat_id=self.chat.id,
video_note=video_note,
@@ -3141,6 +3287,7 @@ async def reply_voice(
reply_to_message_id: int = None,
quote_text: str = None,
quote_entities: List["types.MessageEntity"] = None,
+ ttl_seconds: int = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
"types.ReplyKeyboardMarkup",
@@ -3196,7 +3343,8 @@ async def reply_voice(
Users will receive a notification with no sound.
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message
@@ -3207,6 +3355,11 @@ async def reply_voice(
quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+ ttl_seconds (``int``, *optional*):
+ Self-Destruct Timer.
+ If you set a timer, the voice note will self-destruct in *ttl_seconds*
+ seconds after it was listened.
+
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
Additional interface options. An object for an inline keyboard, custom reply keyboard,
instructions to remove reply keyboard or to force a reply from the user.
@@ -3247,6 +3400,9 @@ async def reply_voice(
if reply_to_message_id is None and quote:
reply_to_message_id = self.id
+ if message_thread_id is None:
+ message_thread_id = self.message_thread_id
+
return await self._client.send_voice(
chat_id=self.chat.id,
voice=voice,
@@ -3259,6 +3415,7 @@ async def reply_voice(
reply_to_message_id=reply_to_message_id,
quote_text=quote_text,
quote_entities=quote_entities,
+ ttl_seconds=ttl_seconds,
reply_markup=reply_markup,
progress=progress,
progress_args=progress_args
@@ -3487,7 +3644,8 @@ async def forward(
For a contact that exists in your Telegram address book you can use his phone number (str).
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
disable_notification (``bool``, *optional*):
Sends the message silently.
@@ -3519,9 +3677,13 @@ async def copy(
caption_entities: List["types.MessageEntity"] = None,
disable_notification: bool = None,
message_thread_id: int = None,
+ reply_to_chat_id: Union[int, str] = None,
reply_to_message_id: int = None,
+ quote_text: str = None,
+ quote_entities: List["types.MessageEntity"] = None,
schedule_date: datetime = None,
protect_content: bool = None,
+ has_spoiler: bool = None,
reply_markup: Union[
"types.InlineKeyboardMarkup",
"types.ReplyKeyboardMarkup",
@@ -3570,17 +3732,29 @@ async def copy(
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
- for forum supergroups only.
+ For supergroups only.
+
+ reply_to_chat_id (``int``, *optional*):
+ If the message is a reply, ID of the original chat.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
+ quote_text (``str``):
+ Text of the quote to be sent.
+
+ quote_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+ List of special entities that appear in quote text, which can be specified instead of *parse_mode*.
+
schedule_date (:py:obj:`~datetime.datetime`, *optional*):
Date when the message will be automatically sent.
protect_content (``bool``, *optional*):
Protects the contents of the sent message from forwarding and saving.
+ has_spoiler (``bool``, *optional*):
+ True, if the message media is covered by a spoiler animation.
+
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
Additional interface options. An object for an inline keyboard, custom reply keyboard,
instructions to remove reply keyboard or to force a reply from the user.
@@ -3610,7 +3784,10 @@ async def copy(
disable_web_page_preview=not self.web_page,
disable_notification=disable_notification,
message_thread_id=message_thread_id,
+ reply_to_chat_id=reply_to_chat_id,
reply_to_message_id=reply_to_message_id,
+ quote_text=quote_text,
+ quote_entities=quote_entities,
schedule_date=schedule_date,
protect_content=protect_content,
reply_markup=self.reply_markup if reply_markup is object else reply_markup
@@ -3622,8 +3799,12 @@ async def copy(
disable_notification=disable_notification,
message_thread_id=message_thread_id,
reply_to_message_id=reply_to_message_id,
+ reply_to_chat_id=reply_to_chat_id,
+ quote_text=quote_text,
+ quote_entities=quote_entities,
schedule_date=schedule_date,
protect_content=protect_content,
+ has_spoiler=has_spoiler,
reply_markup=self.reply_markup if reply_markup is object else reply_markup
)
@@ -3872,7 +4053,7 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None,
else:
await self.reply(button, quote=quote)
- async def react(self, emoji: Union[int, str] = None, big: bool = False) -> bool:
+ async def react(self, emoji: Union[int, str, List[Union[int, str]]] = None, big: bool = False) -> bool:
"""Bound method *react* of :obj:`~pyrogram.types.Message`.
Use as a shortcut for:
@@ -3891,9 +4072,10 @@ async def react(self, emoji: Union[int, str] = None, big: bool = False) -> bool:
await message.react(emoji="🔥")
Parameters:
- emoji (``str``, *optional*):
+ emoji (``int`` | ``str`` | List of ``int`` | ``str``, *optional*):
Reaction emoji.
- Pass "" as emoji (default) to retract the reaction.
+ Pass None as emoji (default) to retract the reaction.
+ Pass list of int or str to react multiple emojis.
big (``bool``, *optional*):
Pass True to show a bigger and longer reaction.
diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py
index de266b2fd..8b37df2db 100644
--- a/pyrogram/types/messages_and_media/sticker.py
+++ b/pyrogram/types/messages_and_media/sticker.py
@@ -45,10 +45,13 @@ class Sticker(Object):
Sticker height.
is_animated (``bool``):
- True, if the sticker is animated
+ True, if the sticker is animated.
is_video (``bool``):
- True, if the sticker is a video sticker
+ True, if the sticker is a video sticker.
+
+ is_premium (``bool``):
+ True, if the sticker is a premium only.
file_name (``str``, *optional*):
Sticker file name.
@@ -84,6 +87,7 @@ def __init__(
height: int,
is_animated: bool,
is_video: bool,
+ is_premium: bool,
file_name: str = None,
mime_type: str = None,
file_size: int = None,
@@ -104,6 +108,7 @@ def __init__(
self.height = height
self.is_animated = is_animated
self.is_video = is_video
+ self.is_premium = is_premium
self.emoji = emoji
self.set_name = set_name
self.thumbs = thumbs
@@ -194,6 +199,7 @@ async def _parse(
),
is_animated=sticker.mime_type == "application/x-tgsticker",
is_video=sticker.mime_type == "video/webm",
+ is_premium=bool(sticker.video_thumbs),
# TODO: mask_position
set_name=set_name,
emoji=sticker_attributes.alt or None,
diff --git a/pyrogram/types/messages_and_media/story.py b/pyrogram/types/messages_and_media/story.py
index 20fc006a6..0260cd399 100644
--- a/pyrogram/types/messages_and_media/story.py
+++ b/pyrogram/types/messages_and_media/story.py
@@ -39,11 +39,23 @@ class Story(Object, Update):
sender_chat (:obj:`~pyrogram.types.Chat`, *optional*):
Sender of the story, sent on behalf of a chat.
+ date (:py:obj:`~datetime.datetime`, *optional*):
+ Date the story was sent.
+
chat (:obj:`~pyrogram.types.Chat`, *optional*):
Conversation the story belongs to.
- date (:py:obj:`~datetime.datetime`, *optional*):
- Date the story was sent.
+ forward_from (:obj:`~pyrogram.types.User`, *optional*):
+ For forwarded stories, sender of the original story.
+
+ forward_sender_name (``str``, *optional*):
+ For stories forwarded from users who have hidden their accounts, name of the user.
+
+ forward_from_chat (:obj:`~pyrogram.types.Chat`, *optional*):
+ For stories forwarded from channels, information about the original channel.
+
+ forward_from_story_id (``int``, *optional*):
+ For stories forwarded from channels, identifier of the original story in the channel.
expire_date (:py:obj:`~datetime.datetime`, *optional*):
Date the story will be expired.
@@ -51,7 +63,7 @@ class Story(Object, Update):
media (:obj:`~pyrogram.enums.MessageMediaType`, *optional*):
The media type of the Story.
This field will contain the enumeration type of the media message.
- You can use ``media = getattr(message, message.media.value)`` to access the media message.
+ You can use ``media = getattr(story, story.media.value)`` to access the media message.
has_protected_content (``bool``, *optional*):
True, if the story can't be forwarded.
@@ -86,9 +98,12 @@ class Story(Object, Update):
caption_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
For text messages, special entities like usernames, URLs, bot commands, etc. that appear in the caption.
- views (:obj:`~pyrogram.types.StoryViews`, *optional*):
+ views (``int``, *optional*):
Stories views.
+ forwards (``int``, *optional*):
+ Stories forwards.
+
privacy (:obj:`~pyrogram.enums.StoryPrivacyRules`, *optional*):
Story privacy.
@@ -97,6 +112,17 @@ class Story(Object, Update):
disallowed_users (List of ``int`` | ``str``, *optional*):
List of user_ids whos denied to view the story.
+
+ reactions (List of :obj:`~pyrogram.types.Reaction`):
+ List of the reactions to this story.
+
+ skipped (``bool``, *optional*):
+ The story is skipped.
+ A story can be skipped in case it was skipped.
+
+ deleted (``bool``, *optional*):
+ The story is deleted.
+ A story can be deleted in case it was deleted or you tried to retrieve a story that doesn't exist yet.
"""
# TODO: Add Media Areas
@@ -108,10 +134,14 @@ def __init__(
id: int,
from_user: "types.User" = None,
sender_chat: "types.Chat" = None,
- chat: "types.Chat" = None,
date: datetime = None,
+ chat: "types.Chat" = None,
+ forward_from: "types.User" = None,
+ forward_sender_name: str = None,
+ forward_from_chat: "types.Chat" = None,
+ forward_from_story_id: int = None,
expire_date: datetime = None,
- media: "enums.MessageMediaType",
+ media: "enums.MessageMediaType" = None,
has_protected_content: bool = None,
photo: "types.Photo" = None,
video: "types.Video" = None,
@@ -123,18 +153,26 @@ def __init__(
selected_contacts: bool = None,
caption: str = None,
caption_entities: List["types.MessageEntity"] = None,
- views: "types.StoryViews" = None,
- privacy: "enums.StoryPrivacy" = None,
+ views: int = None,
+ forwards: int = None,
+ privacy: "enums.StoryPrivacyRules" = None,
allowed_users: List[Union[int, str]] = None,
disallowed_users: List[Union[int, str]] = None,
+ reactions: List["types.Reaction"] = None,
+ skipped: bool = None,
+ deleted: bool = None
):
super().__init__(client)
self.id = id
self.from_user = from_user
self.sender_chat = sender_chat
- self.chat = chat
self.date = date
+ self.chat = chat
+ self.forward_from = forward_from
+ self.forward_sender_name = forward_sender_name
+ self.forward_from_chat = forward_from_chat
+ self.forward_from_story_id = forward_from_story_id
self.expire_date = expire_date
self.media = media
self.has_protected_content = has_protected_content
@@ -149,51 +187,33 @@ def __init__(
self.caption = caption
self.caption_entities = caption_entities
self.views = views
+ self.forwards = forwards
self.privacy = privacy
self.allowed_users = allowed_users
self.disallowed_users = disallowed_users
+ self.reactions = reactions
+ self.skipped = skipped
+ self.deleted = deleted
@staticmethod
async def _parse(
client: "pyrogram.Client",
- stories: raw.base.StoryItem,
+ story: raw.types.StoryItem,
users: dict,
chats: dict,
peer: Union["raw.types.PeerChannel", "raw.types.PeerUser"]
) -> "Story":
- if isinstance(stories, raw.types.StoryItemSkipped):
- return await types.StorySkipped._parse(client, stories, users, chats, peer)
- if isinstance(stories, raw.types.StoryItemDeleted):
- return await types.StoryDeleted._parse(client, stories, users, chats, peer)
-
- entities = [e for e in (types.MessageEntity._parse(client, entity, {}) for entity in stories.entities) if e]
-
- photo = None
- video = None
- from_user = None
- sender_chat = None
- chat = None
- privacy = None
- allowed_users = None
- disallowed_users = None
- media_type = None
-
- if isinstance(stories.media, raw.types.MessageMediaPhoto):
- photo = types.Photo._parse(client, stories.media.photo, stories.media.ttl_seconds)
- media_type = enums.MessageMediaType.PHOTO
- else:
- doc = stories.media.document
- attributes = {type(i): i for i in doc.attributes}
- video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None)
- video = types.Video._parse(client, doc, video_attributes, None)
- media_type = enums.MessageMediaType.VIDEO
-
if isinstance(peer, raw.types.InputPeerSelf):
r = await client.invoke(raw.functions.users.GetUsers(id=[raw.types.InputPeerSelf()]))
peer_id = r[0].id
users.update({i.id: i for i in r})
- elif isinstance(peer, (raw.types.InputPeerUser, raw.types.InputPeerChannel)):
- peer_id = utils.get_input_peer_id(peer)
+ elif isinstance(peer, raw.types.InputPeerUser):
+ peer_id = utils.get_raw_peer_id(peer)
+ elif isinstance(peer, raw.types.InputPeerChannel):
+ peer_id = utils.get_raw_peer_id(peer)
+ if peer_id not in chats:
+ r = await client.invoke(raw.functions.channels.GetChannels(id=[peer]))
+ chats.update({peer_id: r.chats[0]})
else:
peer_id = utils.get_raw_peer_id(peer)
@@ -211,10 +231,65 @@ async def _parse(
else:
users.update({i.id: i for i in r})
+ photo = None
+ video = None
+ from_user = None
+ sender_chat = None
+ chat = None
+ privacy = None
+ allowed_users = None
+ disallowed_users = None
+ media_type = None
+ views = None
+ forwards = None
+ reactions = None
+
from_user = types.User._parse(client, users.get(peer_id, None))
sender_chat = types.Chat._parse_channel_chat(client, chats[peer_id]) if not from_user else None
chat = sender_chat if not from_user else types.Chat._parse_user_chat(client, users.get(peer_id, None))
+ if isinstance(story, raw.types.StoryItemDeleted):
+ return Story(client=client, id=story.id, deleted=True, from_user=from_user, sender_chat=sender_chat, chat=chat)
+ if isinstance(story, raw.types.StoryItemSkipped):
+ return Story(client=client, id=story.id, skipped=True, from_user=from_user, sender_chat=sender_chat, chat=chat)
+ if isinstance(story, raw.types.MessageMediaStory):
+ return Story(client=client, id=story.id, from_user=from_user, sender_chat=sender_chat, chat=chat)
+
+ forward_from = None
+ forward_sender_name = None
+ forward_from_chat = None
+ forward_from_story_id = None
+
+ forward_header = story.fwd_from # type: raw.types.StoryFwdHeader
+
+ if forward_header:
+ fwd_raw_peer_id = utils.get_raw_peer_id(forward_header.from_peer)
+ fwd_peer_id = utils.get_peer_id(forward_header.from_peer)
+
+ if fwd_peer_id > 0:
+ forward_from = types.User._parse(client, users[fwd_raw_peer_id])
+ else:
+ forward_from_chat = types.Chat._parse_channel_chat(client, chats[fwd_raw_peer_id])
+ forward_from_story_id = forward_header.story_id
+
+ if story.views:
+ views=getattr(story.views, "views_count", None)
+ forwards=getattr(story.views, "forwards_count", None)
+ reactions=[
+ types.Reaction._parse_count(client, reaction)
+ for reaction in getattr(story.views, "reactions", [])
+ ] or None
+
+ if isinstance(story.media, raw.types.MessageMediaPhoto):
+ photo = types.Photo._parse(client, story.media.photo, story.media.ttl_seconds)
+ media_type = enums.MessageMediaType.PHOTO
+ else:
+ doc = story.media.document
+ attributes = {type(i): i for i in doc.attributes}
+ video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None)
+ video = types.Video._parse(client, doc, video_attributes, None)
+ media_type = enums.MessageMediaType.VIDEO
+
privacy_map = {
raw.types.PrivacyValueAllowAll: enums.StoriesPrivacyRules.PUBLIC,
raw.types.PrivacyValueAllowContacts: enums.StoriesPrivacyRules.CONTACTS,
@@ -222,7 +297,7 @@ async def _parse(
raw.types.PrivacyValueDisallowAll: enums.StoriesPrivacyRules.SELECTED_USERS,
}
- for priv in stories.privacy:
+ for priv in story.privacy:
privacy = privacy_map.get(type(priv), None)
if isinstance(priv, raw.types.PrivacyValueAllowUsers):
@@ -234,29 +309,37 @@ async def _parse(
elif isinstance(priv, raw.types.PrivacyValueDisallowChatParticipants):
disallowed_users = types.List(types.Chat._parse_chat_chat(client, chats.get(chat_id, None)) for chat_id in priv.chats)
+ entities = [e for e in (types.MessageEntity._parse(client, entity, {}) for entity in story.entities) if e]
+
return Story(
- id=stories.id,
+ id=story.id,
from_user=from_user,
sender_chat=sender_chat,
+ date=utils.timestamp_to_datetime(story.date),
chat=chat,
- date=utils.timestamp_to_datetime(stories.date),
- expire_date=utils.timestamp_to_datetime(stories.expire_date),
+ forward_from=forward_from,
+ forward_sender_name=forward_sender_name,
+ forward_from_chat=forward_from_chat,
+ forward_from_story_id=forward_from_story_id,
+ expire_date=utils.timestamp_to_datetime(story.expire_date),
media=media_type,
- has_protected_content=stories.noforwards,
+ has_protected_content=story.noforwards,
photo=photo,
video=video,
- edited=stories.edited,
- pinned=stories.pinned,
- public=stories.public,
- close_friends=stories.close_friends,
- contacts=stories.contacts,
- selected_contacts=stories.selected_contacts,
- caption=stories.caption,
+ edited=story.edited,
+ pinned=story.pinned,
+ public=story.public,
+ close_friends=story.close_friends,
+ contacts=story.contacts,
+ selected_contacts=story.selected_contacts,
+ caption=story.caption,
caption_entities=entities or None,
- views=types.StoryViews._parse(client, stories.views) if stories.views else None,
+ views=views,
+ forwards=forwards,
privacy=privacy,
allowed_users=allowed_users,
disallowed_users=disallowed_users,
+ reactions=reactions,
client=client
)
@@ -1355,22 +1438,13 @@ async def copy(
Raises:
RPCError: In case of a Telegram RPC error.
"""
- file_id = None
-
- if self.photo:
- file_id = self.photo.file_id
- elif self.video:
- file_id = self.video.file_id
- else:
- raise ValueError("Unknown media type")
-
if caption is None:
caption = self.caption or ""
caption_entities = self.caption_entities
return await self._client.send_story(
chat_id=chat_id,
- media=file_id,
+ media=await self.download(in_memory=True),
caption=caption,
period=period,
protect_content=protect_content,
@@ -1405,15 +1479,9 @@ async def delete(self):
"""
return await self._client.delete_stories(chat_id=self.chat.id, story_ids=self.id)
- async def edit(
+ async def edit_media(
self,
media: Union[str, BinaryIO] = None,
- privacy: "enums.StoriesPrivacyRules" = None,
- allowed_users: List[Union[int, str]] = None,
- disallowed_users: List[Union[int, str]] = None,
- caption: str = None,
- parse_mode: "enums.ParseMode" = None,
- caption_entities: List["types.MessageEntity"] = None
) -> "types.Story":
"""Bound method *edit* of :obj:`~pyrogram.types.Story`.
@@ -1429,12 +1497,9 @@ async def edit(
Example:
.. code-block:: python
- await story.edit_caption("hello")
+ await story.edit_media("new_video.mp4")
Parameters:
- story_id (``int``):
- Unique identifier (int) of the target story.
-
media (``str`` | ``BinaryIO``, *optional*):
New story media.
Pass a file_id as string to send a photo that exists on the Telegram servers,
@@ -1442,46 +1507,16 @@ async def edit(
pass a file path as string to upload a new photo that exists on your local machine, or
pass a binary file-like object with its attribute ".name" set for in-memory uploads.
- privacy (:obj:`~pyrogram.enums.StoriesPrivacyRules`, *optional*):
- Story privacy.
-
- allowed_users (List of ``int``, *optional*):
- List of user_id or chat_id of chat users who are allowed to view stories.
- Note: chat_id available only with :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS`.
- Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.CLOSE_FRIENDS`
- and :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS` only
-
- disallowed_users (List of ``int``, *optional*):
- List of user_id whos disallow to view the stories.
- Note: Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.PUBLIC`
- and :obj:`~pyrogram.enums.StoriesPrivacyRules.CONTACTS` only
-
- caption (``str``, *optional*):
- Story caption, 0-1024 characters.
-
- parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
- By default, texts are parsed using both Markdown and HTML styles.
- You can combine both syntaxes together.
-
- caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
- List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
-
Returns:
On success, the edited :obj:`~pyrogram.types.Story` is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
- return await self._client.edit_story(
+ return await self._client.edit_story_media(
chat_id=self.chat.id,
story_id=self.id,
- media=media,
- privacy=privacy,
- allowed_users=allowed_users,
- disallowed_users=disallowed_users,
- caption=caption,
- parse_mode=parse_mode,
- caption_entities=caption_entities
+ media=media
)
async def edit_caption(
@@ -1496,7 +1531,7 @@ async def edit_caption(
.. code-block:: python
- await client.edit_story(
+ await client.edit_story_caption(
story_id=story.id,
caption="hello"
)
@@ -1523,7 +1558,7 @@ async def edit_caption(
Raises:
RPCError: In case of a Telegram RPC error.
"""
- return await self._client.edit_story(
+ return await self._client.edit_story_caption(
chat_id=self.chat.id,
story_id=self.id,
caption=caption,
@@ -1543,7 +1578,7 @@ async def edit_privacy(
.. code-block:: python
- await client.edit_story(
+ await client.edit_story_privacy(
story_id=story.id,
privacy=enums.StoriesPrivacyRules.PUBLIC
)
@@ -1557,13 +1592,13 @@ async def edit_privacy(
privacy (:obj:`~pyrogram.enums.StoriesPrivacyRules`, *optional*):
Story privacy.
- allowed_users (List of ``int``, *optional*):
+ allowed_users (List of ``int`` | ``str``, *optional*):
List of user_id or chat_id of chat users who are allowed to view stories.
Note: chat_id available only with :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS`.
Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.CLOSE_FRIENDS`
and :obj:`~pyrogram.enums.StoriesPrivacyRules.SELECTED_USERS` only
- disallowed_users (List of ``int``, *optional*):
+ disallowed_users (List of ``int`` | ``str``, *optional*):
List of user_id whos disallow to view the stories.
Note: Works with :obj:`~pyrogram.enums.StoriesPrivacyRules.PUBLIC`
and :obj:`~pyrogram.enums.StoriesPrivacyRules.CONTACTS` only
@@ -1574,7 +1609,7 @@ async def edit_privacy(
Raises:
RPCError: In case of a Telegram RPC error.
"""
- return await self._client.edit_story(
+ return await self._client.edit_story_privacy(
chat_id=self.chat.id,
story_id=self.id,
privacy=privacy,
@@ -1673,7 +1708,8 @@ async def forward(
For a contact that exists in your Telegram address book you can use his phone number (str).
message_thread_id (``int``, *optional*):
- Unique identifier of a message thread to which the message belongs; for supergroups only
+ Unique identifier of a message thread to which the message belongs.
+ For supergroups only.
disable_notification (``bool``, *optional*):
Sends the message silently.
@@ -1771,3 +1807,25 @@ async def download(
progress=progress,
progress_args=progress_args,
)
+
+ async def read(self) -> List[int]:
+ """Bound method *read* of :obj:`~pyrogram.types.Story`.
+
+ Example:
+ .. code-block:: python
+
+ await story.read()
+
+ Returns:
+ List of ``int``: On success, a list of read stories is returned.
+
+ Example:
+ .. code-block:: python
+
+ # Read stories
+ await app.read_stories(chat_id)
+ """
+ return await self._client.read_stories(
+ chat_id=self.chat.id,
+ max_id=self.id
+ )
\ No newline at end of file
diff --git a/pyrogram/types/messages_and_media/story_deleted.py b/pyrogram/types/messages_and_media/story_deleted.py
deleted file mode 100644
index c6c707c52..000000000
--- a/pyrogram/types/messages_and_media/story_deleted.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# Pyrogram - Telegram MTProto API Client Library for Python
-# Copyright (C) 2017-present Dan
-#
-# 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 pyrogram import raw, types, utils
-from pyrogram.errors import PeerIdInvalid
-from typing import Union
-from ..object import Object
-from ..update import Update
-
-class StoryDeleted(Object, Update):
- """A deleted story.
-
- Parameters:
- id (``int``):
- Unique story identifier.
-
- from_user (:obj:`~pyrogram.types.User`, *optional*):
- Sender of the story.
-
- sender_chat (:obj:`~pyrogram.types.Chat`, *optional*):
- Sender of the story. If the story is from channel.
- """
-
- def __init__(
- self,
- *,
- client: "pyrogram.Client" = None,
- id: int,
- from_user: "types.User" = None,
- sender_chat: "types.Chat" = None
- ):
- super().__init__(client)
-
- self.id = id
- self.from_user = from_user
- self.sender_chat = sender_chat
-
- @staticmethod
- async def _parse(
- client: "pyrogram.Client",
- stories: raw.base.StoryItem,
- users: dict,
- chats: dict,
- peer: Union["raw.types.PeerChannel", "raw.types.PeerUser"]
- ) -> "StoryDeleted":
- from_user = None
- sender_chat = None
-
- if isinstance(peer, raw.types.InputPeerSelf):
- r = await client.invoke(raw.functions.users.GetUsers(id=[raw.types.InputPeerSelf()]))
- peer_id = r[0].id
- users.update({i.id: i for i in r})
- elif isinstance(peer, (raw.types.InputPeerUser, raw.types.InputPeerChannel)):
- peer_id = utils.get_input_peer_id(peer)
- else:
- peer_id = utils.get_raw_peer_id(peer)
-
- if isinstance(peer, (raw.types.PeerUser, raw.types.InputPeerUser)) and peer_id not in users:
- try:
- r = await client.invoke(
- raw.functions.users.GetUsers(
- id=[
- await client.resolve_peer(peer_id)
- ]
- )
- )
- except PeerIdInvalid:
- pass
- else:
- users.update({i.id: i for i in r})
-
- from_user = types.User._parse(client, users.get(peer_id, None))
- sender_chat = types.Chat._parse_channel_chat(client, chats[peer_id]) if not from_user else None
-
- return StoryDeleted(
- id=stories.id,
- from_user=from_user,
- sender_chat=sender_chat,
- client=client
- )
\ No newline at end of file
diff --git a/pyrogram/types/messages_and_media/story_skipped.py b/pyrogram/types/messages_and_media/story_skipped.py
deleted file mode 100644
index 1fb9a9f42..000000000
--- a/pyrogram/types/messages_and_media/story_skipped.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# Pyrogram - Telegram MTProto API Client Library for Python
-# Copyright (C) 2017-present Dan
-#
-# 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 datetime import datetime
-from pyrogram import raw, types, utils
-from pyrogram.errors import PeerIdInvalid
-from typing import Union
-from ..object import Object
-from ..update import Update
-
-class StorySkipped(Object, Update):
- """A skipped story.
-
- Parameters:
- id (``int``):
- Unique story identifier.
-
- from_user (:obj:`~pyrogram.types.User`, *optional*):
- Sender of the story.
-
- sender_chat (:obj:`~pyrogram.types.Chat`, *optional*):
- Sender of the story. If the story is from channel.
-
- date (:py:obj:`~datetime.datetime`, *optional*):
- Date the story was sent.
-
- expire_date (:py:obj:`~datetime.datetime`, *optional*):
- Date the story will be expired.
-
- close_friends (``bool``, *optional*):
- True, if the Story is shared with close_friends only.
- """
-
- def __init__(
- self,
- *,
- client: "pyrogram.Client" = None,
- id: int,
- from_user: "types.User" = None,
- sender_chat: "types.Chat" = None,
- date: datetime,
- expire_date: datetime,
- close_friends: bool = None
- ):
- super().__init__(client)
-
- self.id = id
- self.from_user = from_user
- self.sender_chat = sender_chat
- self.date = date
- self.expire_date = expire_date
- self.close_friends = close_friends
-
- @staticmethod
- async def _parse(
- client: "pyrogram.Client",
- stories: raw.base.StoryItem,
- users: dict,
- chats: dict,
- peer: Union["raw.types.PeerChannel", "raw.types.PeerUser"]
- ) -> "StorySkipped":
- from_user = None
- sender_chat = None
-
- if isinstance(peer, raw.types.InputPeerSelf):
- r = await client.invoke(raw.functions.users.GetUsers(id=[raw.types.InputPeerSelf()]))
- peer_id = r[0].id
- users.update({i.id: i for i in r})
- elif isinstance(peer, (raw.types.InputPeerUser, raw.types.InputPeerChannel)):
- peer_id = utils.get_input_peer_id(peer)
- else:
- peer_id = utils.get_raw_peer_id(peer)
-
- if isinstance(peer, (raw.types.PeerUser, raw.types.InputPeerUser)) and peer_id not in users:
- try:
- r = await client.invoke(
- raw.functions.users.GetUsers(
- id=[
- await client.resolve_peer(peer_id)
- ]
- )
- )
- except PeerIdInvalid:
- pass
- else:
- users.update({i.id: i for i in r})
-
- from_user = types.User._parse(client, users.get(peer_id, None))
- sender_chat = types.Chat._parse_channel_chat(client, chats[peer_id]) if not from_user else None
-
- return StorySkipped(
- id=stories.id,
- from_user=from_user,
- sender_chat=sender_chat,
- date=utils.timestamp_to_datetime(stories.date),
- expire_date=utils.timestamp_to_datetime(stories.expire_date),
- close_friends=stories.close_friends,
- client=client
- )
\ No newline at end of file
diff --git a/pyrogram/types/messages_and_media/story_views.py b/pyrogram/types/messages_and_media/story_views.py
deleted file mode 100644
index 6e2af2d4f..000000000
--- a/pyrogram/types/messages_and_media/story_views.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# Pyrogram - Telegram MTProto API Client Library for Python
-# Copyright (C) 2017-present Dan
-#
-# 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 import raw, types
-from typing import List
-from ..object import Object
-
-class StoryViews(Object):
- """Contains information about a story viewers.
-
- Parameters:
- views_count (``int`` ``32-bit``):
- Views count.
-
- has_viewers (``bool``, *optional*):
- Has viewers.
-
- forwards_count (``int`` ``32-bit``, *optional*):
- Forwards count.
-
- reactions (List of :obj:`~pyrogram.types.Reaction`, *optional*):
- Reactions list.
-
- reactions_count (``int`` ``32-bit``, *optional*):
- Reactions count.
-
- recent_viewers (List of ``int`` ``64-bit``, *optional*):
- Viewers list.
- """
-
- def __init__(
- self, *,
- views_count: int,
- has_viewers: bool = None,
- forwards_count: int = None,
- reactions: List["types.Reaction"] = None,
- reactions_count: int = None,
- recent_viewers: List[int] = None
- ):
- super().__init__()
-
- self.views_count = views_count
- self.has_viewers = has_viewers
- self.forwards_count = forwards_count
- self.reactions = reactions
- self.reactions_count = reactions_count
- self.recent_viewers = recent_viewers
-
- @staticmethod
- def _parse(client, story_views: "raw.types.StoryViews") -> "StoryViews":
- return StoryViews(
- views_count=getattr(story_views, "views_count", None),
- has_viewers=getattr(story_views, "has_viewers", None),
- forwards_count=getattr(story_views, "forwards_count", None),
- reactions=[
- types.Reaction._parse_count(client, reaction)
- for reaction in getattr(story_views, "reactions", [])
- ] or None,
- recent_viewers=getattr(story_views, "recent_viewers", None) or None,
- )
diff --git a/pyrogram/types/messages_and_media/web_page.py b/pyrogram/types/messages_and_media/web_page.py
index 34e51d88c..9cf55478d 100644
--- a/pyrogram/types/messages_and_media/web_page.py
+++ b/pyrogram/types/messages_and_media/web_page.py
@@ -77,6 +77,21 @@ class WebPage(Object):
embed_height (``int``, *optional*):
Embedded content height.
+ has_large_media (``bool``, *optional*):
+ Whether the webpage preview is large.
+
+ force_large_media (``bool``, *optional*):
+ Whether the webpage preview is forced large.
+
+ force_small_media (``bool``, *optional*):
+ Whether the webpage preview is forced small.
+
+ manual (``bool``, *optional*):
+ Whether the webpage preview was changed by the user.
+
+ safe (``bool``, *optional*):
+ Whether the webpage preview is safe.
+
duration (``int``, *optional*):
Unknown at the time of writing.
@@ -104,6 +119,11 @@ def __init__(
embed_type: str = None,
embed_width: int = None,
embed_height: int = None,
+ has_large_media: bool = None,
+ force_large_media: bool = None,
+ force_small_media: bool = None,
+ manual: bool = None,
+ safe: bool = None,
duration: int = None,
author: str = None
):
@@ -125,11 +145,23 @@ def __init__(
self.embed_type = embed_type
self.embed_width = embed_width
self.embed_height = embed_height
+ self.has_large_media = has_large_media
+ self.force_large_media = force_large_media
+ self.force_small_media = force_small_media
+ self.manual = manual
+ self.safe = safe
self.duration = duration
self.author = author
@staticmethod
- def _parse(client, webpage: "raw.types.WebPage") -> "WebPage":
+ def _parse(
+ client,
+ webpage: "raw.types.WebPage",
+ force_large_media: bool = None,
+ force_small_media: bool = None,
+ manual: bool = None,
+ safe: bool = None
+ ) -> "WebPage":
audio = None
document = None
photo = None
@@ -182,6 +214,11 @@ def _parse(client, webpage: "raw.types.WebPage") -> "WebPage":
embed_type=webpage.embed_type,
embed_width=webpage.embed_width,
embed_height=webpage.embed_height,
+ has_large_media=webpage.has_large_media,
+ force_large_media=force_large_media,
+ force_small_media=force_small_media,
+ manual=manual,
+ safe=safe,
duration=webpage.duration,
author=webpage.author
)
diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py
index d7b1a382d..cfc2f5eab 100644
--- a/pyrogram/types/user_and_chats/__init__.py
+++ b/pyrogram/types/user_and_chats/__init__.py
@@ -18,6 +18,7 @@
from .chat import Chat
from .chat_admin_with_invite_links import ChatAdminWithInviteLinks
+from .chat_color import ChatColor
from .chat_event import ChatEvent
from .chat_event_filter import ChatEventFilter
from .chat_invite_link import ChatInviteLink
@@ -32,10 +33,9 @@
from .chat_reactions import ChatReactions
from .dialog import Dialog
from .emoji_status import EmojiStatus
+from .folder import Folder
from .invite_link_importer import InviteLinkImporter
from .restriction import Restriction
-from .peer_channel import PeerChannel
-from .peer_user import PeerUser
from .user import User
from .username import Username
from .video_chat_ended import VideoChatEnded
@@ -50,8 +50,6 @@
"ChatPhoto",
"ChatPreview",
"Dialog",
- "PeerChannel",
- "PeerUser",
"User",
"Username",
"Restriction",
@@ -60,6 +58,7 @@
"ChatInviteLink",
"InviteLinkImporter",
"ChatAdminWithInviteLinks",
+ "ChatColor",
"VideoChatStarted",
"VideoChatEnded",
"VideoChatMembersInvited",
@@ -69,5 +68,6 @@
"ChatPrivileges",
"ChatJoiner",
"EmojiStatus",
+ "Folder",
"ChatReactions"
]
\ No newline at end of file
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index ce1540c3c..001f570e0 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -37,12 +37,12 @@ class Chat(Object):
Type of chat.
is_forum (``bool``, *optional*):
- True, if the supergroup chat is a forum
+ True, if the supergroup chat is a forum.
is_verified (``bool``, *optional*):
True, if this chat has been verified by Telegram. Supergroups, channels and bots only.
- is_participants_hidden (``bool``, *optional*):
+ is_members_hidden (``bool``, *optional*):
True, if the chat members are hidden.
is_restricted (``bool``, *optional*):
@@ -52,19 +52,25 @@ class Chat(Object):
is_creator (``bool``, *optional*):
True, if this chat owner is the current user. Supergroups, channels and groups only.
+ is_admin (``bool``, *optional*):
+ True, if the current user is admin. Supergroups, channels and groups only.
+
is_scam (``bool``, *optional*):
True, if this chat has been flagged for scam.
is_fake (``bool``, *optional*):
True, if this chat has been flagged for impersonation.
- is_support (``bool``):
+ is_deactivated (``bool``, *optional*):
+ True, if this chat has been flagged for deactivated.
+
+ is_support (``bool``, *optional*):
True, if this chat is part of the Telegram support team. Users and bots only.
- is_stories_hidden (``bool``):
+ is_stories_hidden (``bool``, *optional*):
True, if this chat has hidden stories.
- is_stories_unavailable (``bool``):
+ is_stories_unavailable (``bool``, *optional*):
True, if this chat stories is unavailable.
title (``str``, *optional*):
@@ -88,6 +94,12 @@ class Chat(Object):
photo (:obj:`~pyrogram.types.ChatPhoto`, *optional*):
Chat photo. Suitable for downloads only.
+ stories (List of :obj:`~pyrogram.types.Story`, *optional*):
+ The list of chat's stories if available.
+
+ wallpaper (:obj:`~pyrogram.types.Document`, *optional*):
+ Chat wallpaper.
+
bio (``str``, *optional*):
Bio of the other party in a private chat.
Returned only in :meth:`~pyrogram.Client.get_chat`.
@@ -102,6 +114,9 @@ class Chat(Object):
It is accurate only in case the owner has set the chat photo, otherwise the dc_id will be the one assigned
to the administrator who set the current chat photo.
+ folder_id (``int``, *optional*):
+ The folder identifier where the chat is located.
+
has_protected_content (``bool``, *optional*):
True, if messages from the chat can't be forwarded to other chats.
@@ -148,11 +163,14 @@ class Chat(Object):
Available reactions in the chat.
Returned only in :meth:`~pyrogram.Client.get_chat`.
- color (``int``, *optional*)
- Chat color.
+ level (``int``, *optional*):
+ Channel boosts level.
+
+ reply_color (:obj:`~pyrogram.types.ChatColor`, *optional*):
+ Chat reply color.
- background_emoji_id (``int``, *optional*)
- Chat background emoji id.
+ profile_color (:obj:`~pyrogram.types.ChatColor`, *optional*):
+ Chat profile color.
"""
def __init__(
@@ -163,11 +181,13 @@ def __init__(
type: "enums.ChatType",
is_forum: bool = None,
is_verified: bool = None,
- is_participants_hidden: bool = None,
+ is_members_hidden: bool = None,
is_restricted: bool = None,
is_creator: bool = None,
+ is_admin: bool = None,
is_scam: bool = None,
is_fake: bool = None,
+ is_deactivated: bool = None,
is_support: bool = None,
is_stories_hidden: bool = None,
is_stories_unavailable: bool = None,
@@ -177,9 +197,12 @@ def __init__(
first_name: str = None,
last_name: str = None,
photo: "types.ChatPhoto" = None,
+ stories: List["types.Story"] = None,
+ wallpaper: "types.Document" = None,
bio: str = None,
description: str = None,
dc_id: int = None,
+ folder_id: int = None,
has_protected_content: bool = None,
invite_link: str = None,
pinned_message=None,
@@ -192,8 +215,9 @@ def __init__(
linked_chat: "types.Chat" = None,
send_as_chat: "types.Chat" = None,
available_reactions: Optional["types.ChatReactions"] = None,
- color: int = None,
- background_emoji_id: int = None
+ level: int = None,
+ reply_color: "types.ChatColor" = None,
+ profile_color: "types.ChatColor" = None
):
super().__init__(client)
@@ -201,11 +225,13 @@ def __init__(
self.type = type
self.is_forum = is_forum
self.is_verified = is_verified
- self.is_participants_hidden = is_participants_hidden
+ self.is_members_hidden = is_members_hidden
self.is_restricted = is_restricted
self.is_creator = is_creator
+ self.is_admin = is_admin
self.is_scam = is_scam
self.is_fake = is_fake
+ self.is_deactivated = is_deactivated
self.is_support = is_support
self.is_stories_hidden = is_stories_hidden
self.is_stories_unavailable = is_stories_unavailable
@@ -215,9 +241,12 @@ def __init__(
self.first_name = first_name
self.last_name = last_name
self.photo = photo
+ self.stories = stories
+ self.wallpaper = wallpaper
self.bio = bio
self.description = description
self.dc_id = dc_id
+ self.folder_id = folder_id
self.has_protected_content = has_protected_content
self.invite_link = invite_link
self.pinned_message = pinned_message
@@ -230,8 +259,9 @@ def __init__(
self.linked_chat = linked_chat
self.send_as_chat = send_as_chat
self.available_reactions = available_reactions
- self.color = color
- self.background_emoji_id = background_emoji_id
+ self.level = level
+ self.reply_color = reply_color
+ self.profile_color = profile_color
@staticmethod
def _parse_user_chat(client, user: raw.types.User) -> "Chat":
@@ -245,15 +275,15 @@ def _parse_user_chat(client, user: raw.types.User) -> "Chat":
is_scam=getattr(user, "scam", None),
is_fake=getattr(user, "fake", None),
is_support=getattr(user, "support", None),
- username=user.username,
+ username=user.username or (user.usernames[0].username if user.usernames else None),
usernames=types.List([types.Username._parse(r) for r in user.usernames]) or None,
first_name=user.first_name,
last_name=user.last_name,
photo=types.ChatPhoto._parse(client, user.photo, peer_id, user.access_hash),
restrictions=types.List([types.Restriction._parse(r) for r in user.restriction_reason]) or None,
dc_id=getattr(getattr(user, "photo", None), "dc_id", None),
- color=getattr(user, "color", None),
- background_emoji_id=getattr(user, "background_emoji_id", None),
+ reply_color=types.ChatColor._parse(getattr(user, "color", None)),
+ profile_color=types.ChatColor._parse_profile_color(getattr(user, "profile_color", None)),
client=client
)
@@ -261,12 +291,15 @@ def _parse_user_chat(client, user: raw.types.User) -> "Chat":
def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat":
peer_id = -chat.id
usernames = getattr(chat, "usernames", [])
+ admin_rights = getattr(chat, "admin_rights", None)
return Chat(
id=peer_id,
type=enums.ChatType.GROUP,
title=chat.title,
is_creator=getattr(chat, "creator", None),
+ is_admin=True if admin_rights else None,
+ is_deactivated=getattr(chat, "deactivated", None),
usernames=types.List([types.Username._parse(r) for r in usernames]) or None,
photo=types.ChatPhoto._parse(client, getattr(chat, "photo", None), peer_id, 0),
permissions=types.ChatPermissions._parse(getattr(chat, "default_banned_rights", None)),
@@ -281,14 +314,16 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat":
peer_id = utils.get_channel_id(channel.id)
restriction_reason = getattr(channel, "restriction_reason", [])
usernames = getattr(channel, "usernames", [])
+ admin_rights = getattr(channel, "admin_rights", None)
return Chat(
id=peer_id,
type=enums.ChatType.SUPERGROUP if getattr(channel, "megagroup", None) else enums.ChatType.CHANNEL,
- is_forum=getattr(channel, "forum", None),
+ is_forum=getattr(channel, "forum", None) if getattr(channel, "megagroup", None) else None,
is_verified=getattr(channel, "verified", None),
is_restricted=getattr(channel, "restricted", None),
is_creator=getattr(channel, "creator", None),
+ is_admin=True if admin_rights else None,
is_scam=getattr(channel, "scam", None),
is_fake=getattr(channel, "fake", None),
is_stories_hidden=getattr(channel, "stories_hidden", None),
@@ -303,8 +338,9 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat":
members_count=getattr(channel, "participants_count", None),
dc_id=getattr(getattr(channel, "photo", None), "dc_id", None),
has_protected_content=getattr(channel, "noforwards", None),
- color=getattr(channel, "color", None),
- background_emoji_id=getattr(channel, "background_emoji_id", None),
+ level=getattr(channel, "level", None),
+ reply_color=types.ChatColor._parse(getattr(channel, "color", None)),
+ profile_color=types.ChatColor._parse(getattr(channel, "profile_color", None)),
client=client
)
@@ -330,9 +366,9 @@ def _parse(
@staticmethod
def _parse_dialog(client, peer, users: dict, chats: dict):
- if isinstance(peer, raw.types.PeerUser):
+ if isinstance(peer, (raw.types.PeerUser, raw.types.InputPeerUser)):
return Chat._parse_user_chat(client, users[peer.user_id])
- elif isinstance(peer, raw.types.PeerChat):
+ elif isinstance(peer, (raw.types.PeerChat, raw.types.InputPeerChat)):
return Chat._parse_chat_chat(client, chats[peer.chat_id])
else:
return Chat._parse_channel_chat(client, chats[peer.channel_id])
@@ -347,12 +383,27 @@ async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw.
parsed_chat = Chat._parse_user_chat(client, users[full_user.id])
parsed_chat.bio = full_user.about
+ parsed_chat.folder_id = getattr(full_user, "folder_id", None)
if full_user.pinned_msg_id:
parsed_chat.pinned_message = await client.get_messages(
parsed_chat.id,
message_ids=full_user.pinned_msg_id
)
+
+ if getattr(full_user, "stories"):
+ peer_stories: raw.types.PeerStories = full_user.stories
+ parsed_chat.stories = types.List(
+ [
+ await types.Story._parse(
+ client, story, users, chats, peer_stories.peer
+ )
+ for story in peer_stories.stories
+ ]
+ ) or None
+
+ if getattr(full_user, "wallpaper") and isinstance(full_user.wallpaper, raw.types.WallPaper):
+ parsed_chat.wallpaper = types.Document._parse(client, full_user.wallpaper.document, "wallpaper.jpg")
else:
full_chat = chat_full.full_chat
chat_raw = chats[full_chat.id]
@@ -370,7 +421,8 @@ async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw.
# TODO: Add StickerSet type
parsed_chat.can_set_sticker_set = full_chat.can_set_stickers
parsed_chat.sticker_set_name = getattr(full_chat.stickerset, "short_name", None)
- parsed_chat.is_participants_hidden = full_chat.participants_hidden
+ parsed_chat.is_members_hidden = full_chat.participants_hidden
+ parsed_chat.folder_id = getattr(full_chat, "folder_id", None)
linked_chat_raw = chats.get(full_chat.linked_chat_id, None)
@@ -387,6 +439,20 @@ async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw.
parsed_chat.send_as_chat = Chat._parse_chat(client, send_as_raw)
+ if getattr(full_chat, "stories"):
+ peer_stories: raw.types.PeerStories = full_chat.stories
+ parsed_chat.stories = types.List(
+ [
+ await types.Story._parse(
+ client, story, users, chats, peer_stories.peer
+ )
+ for story in peer_stories.stories
+ ]
+ ) or None
+
+ if getattr(full_chat, "wallpaper") and isinstance(full_chat.wallpaper, raw.types.WallPaper):
+ parsed_chat.wallpaper = types.Document._parse(client, full_chat.wallpaper.document, "wallpaper.jpg")
+
if full_chat.pinned_msg_id:
parsed_chat.pinned_message = await client.get_messages(
parsed_chat.id,
@@ -411,7 +477,7 @@ def _parse_chat(client, chat: Union[raw.types.Chat, raw.types.User, raw.types.Ch
@property
def full_name(self) -> str:
- return " ".join(filter(None, [self.first_name, self.last_name])) or None
+ return " ".join(filter(None, [self.first_name, self.last_name])) or self.title or None
async def archive(self):
"""Bound method *archive* of :obj:`~pyrogram.types.Chat`.
@@ -595,6 +661,31 @@ async def set_photo(
video_start_ts=video_start_ts
)
+ async def set_ttl(self, ttl_seconds: int) -> bool:
+ """Bound method *set_ttl* of :obj:`~pyrogram.types.Chat`.
+
+ Use as a shortcut for:
+
+ .. code-block:: python
+
+ await client.set_chat_ttl(
+ chat_id=chat_id,
+ ttl_seconds=ttl_seconds
+ )
+
+ Example:
+ .. code-block:: python
+
+ await chat.set_ttl(86400)
+
+ Returns:
+ ``bool``: True on success.
+ """
+ return await self._client.set_chat_ttl(
+ chat_id=self.id,
+ ttl_seconds=ttl_seconds
+ )
+
async def ban_member(
self,
user_id: Union[int, str],
@@ -880,7 +971,7 @@ def get_members(
query: str = "",
limit: int = 0,
filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.SEARCH
- ) -> Optional[AsyncGenerator["types.ChatMember", None]]:
+ ) -> AsyncGenerator["types.ChatMember", None]:
"""Bound method *get_members* of :obj:`~pyrogram.types.Chat`.
Use as a shortcut for:
@@ -1016,3 +1107,50 @@ async def unpin_all_messages(self) -> bool:
"""
return await self._client.unpin_all_chat_messages(self.id)
+
+ async def mute(self, mute_until: datetime = None) -> bool:
+ """Bound method *mute* of :obj:`~pyrogram.types.Chat`.
+
+ Use as a shortcut for:
+
+ .. code-block:: python
+
+ client.update_chat_notifications(chat_id, mute=True, mute_until=mute_until)
+
+ Parameters:
+ mute (``bool``, *optional*):
+ Pass True if you want to mute chat.
+
+ until_date (:py:obj:`~datetime.datetime`, *optional*):
+ Date when the user will be unmuted. Defaults to forever.
+
+ Example:
+ .. code-block:: python
+
+ chat.mute()
+
+ Returns:
+ ``bool``: On success, True is returned.
+ """
+
+ return await self._client.update_chat_notifications(self.id, mute=True, mute_until=mute_until)
+
+ async def unmute(self) -> bool:
+ """Bound method *unmute* of :obj:`~pyrogram.types.Chat`.
+
+ Use as a shortcut for:
+
+ .. code-block:: python
+
+ client.update_chat_notifications(chat_id, mute=False)
+
+ Example:
+ .. code-block:: python
+
+ chat.unmute()
+
+ Returns:
+ ``bool``: On success, True is returned.
+ """
+
+ return await self._client.update_chat_notifications(self.id, mute=False)
diff --git a/pyrogram/types/user_and_chats/chat_color.py b/pyrogram/types/user_and_chats/chat_color.py
new file mode 100644
index 000000000..49dee0ad6
--- /dev/null
+++ b/pyrogram/types/user_and_chats/chat_color.py
@@ -0,0 +1,64 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import Optional, Union
+
+from pyrogram import raw
+from pyrogram import enums
+from ..object import Object
+
+
+class ChatColor(Object):
+ """Reply or profile color status.
+
+ Parameters:
+ color (:obj:`~pyrogram.enums.ReplyColor` | :obj:`~pyrogram.enums.ProfileColor`, *optional*):
+ Color type.
+
+ background_emoji_id (``int``, *optional*):
+ Unique identifier of the custom emoji.
+ """
+
+ def __init__(
+ self,
+ *,
+ color: Union["enums.ReplyColor", "enums.ProfileColor"] = None,
+ background_emoji_id: int = None
+ ):
+ self.color = color
+ self.background_emoji_id = background_emoji_id
+
+ @staticmethod
+ def _parse(color: "raw.types.PeerColor" = None) -> Optional["ChatColor"]:
+ if not color:
+ return None
+
+ return ChatColor(
+ color=enums.ReplyColor(color.color) if getattr(color, "color", None) else None,
+ background_emoji_id=getattr(color, "background_emoji_id", None)
+ )
+
+ @staticmethod
+ def _parse_profile_color(color: "raw.types.PeerColor" = None) -> Optional["ChatColor"]:
+ if not color:
+ return None
+
+ return ChatColor(
+ color=enums.ProfileColor(color.color) if getattr(color, "color", None) else None,
+ background_emoji_id=getattr(color, "background_emoji_id", None)
+ )
diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py
index 2c4ddb411..4f9da9cb7 100644
--- a/pyrogram/types/user_and_chats/chat_event.py
+++ b/pyrogram/types/user_and_chats/chat_event.py
@@ -454,16 +454,16 @@ async def _parse(
action = enums.ChatEventAction.INVITE_LINK_DELETED
elif isinstance(action, raw.types.ChannelAdminLogEventActionCreateTopic):
- created_forum_topic = types.ForumTopic._parse(action.topic)
+ created_forum_topic = types.ForumTopic._parse(client, action.topic, users=users, chats=chats)
action = enums.ChatEventAction.CREATED_FORUM_TOPIC
elif isinstance(action, raw.types.ChannelAdminLogEventActionEditTopic):
- old_forum_topic = types.ForumTopic._parse(action.prev_topic)
- new_forum_topic = types.ForumTopic._parse(action.new_topic)
+ old_forum_topic = types.ForumTopic._parse(client, action.prev_topic, users=users, chats=chats)
+ new_forum_topic = types.ForumTopic._parse(client, action.new_topic, users=users, chats=chats)
action = enums.ChatEventAction.EDITED_FORUM_TOPIC
elif isinstance(action, raw.types.ChannelAdminLogEventActionDeleteTopic):
- created_forum_topic = types.ForumTopic._parse(action.topic)
+ created_forum_topic = types.ForumTopic._parse(client, action.topic, users=users, chats=chats)
action = enums.ChatEventAction.DELETED_FORUM_TOPIC
else:
diff --git a/pyrogram/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py
index d1c99c70d..a96f59dac 100644
--- a/pyrogram/types/user_and_chats/chat_permissions.py
+++ b/pyrogram/types/user_and_chats/chat_permissions.py
@@ -56,7 +56,7 @@ class ChatPermissions(Object):
can_manage_topics (``bool``, *optional*):
True, if the user is allowed to create, rename, close, and reopen forum topics.
- supergroups only.
+ Supergroups only.
"""
def __init__(
diff --git a/pyrogram/types/user_and_chats/chat_privileges.py b/pyrogram/types/user_and_chats/chat_privileges.py
index 3507614c2..ab53e7640 100644
--- a/pyrogram/types/user_and_chats/chat_privileges.py
+++ b/pyrogram/types/user_and_chats/chat_privileges.py
@@ -32,6 +32,10 @@ class ChatPrivileges(Object):
can_delete_messages (``bool``, *optional*):
True, if the administrator can delete messages of other users.
+ can_delete_stories (``bool``, *optional*):
+ Channels only.
+ True, if the administrator can delete stories of other users.
+
can_manage_video_chats (``bool``, *optional*):
Groups and supergroups only.
True, if the administrator can manage video chats (also called group calls).
@@ -51,10 +55,18 @@ class ChatPrivileges(Object):
Channels only.
True, if the administrator can post messages in the channel.
+ can_post_stories (``bool``, *optional*):
+ Channels only.
+ True, if the administrator can post stories in the channel.
+
can_edit_messages (``bool``, *optional*):
Channels only.
True, if the administrator can edit messages of other users and can pin messages.
+ can_edit_stories (``bool``, *optional*):
+ Channels only.
+ True, if the administrator can edit stories of other users.
+
can_invite_users (``bool``, *optional*):
True, if the user is allowed to invite new users to the chat.
@@ -63,7 +75,7 @@ class ChatPrivileges(Object):
True, if the user is allowed to pin messages.
can_manage_topics (``bool``, *optional*):
- supergroups only.
+ Supergroups only.
True, if the user is allowed to create, rename, close, and reopen forum topics.
is_anonymous (``bool``, *optional*):
@@ -75,12 +87,15 @@ def __init__(
*,
can_manage_chat: bool = True,
can_delete_messages: bool = False,
+ can_delete_stories: bool = False, # Channels only
can_manage_video_chats: bool = False, # Groups and supergroups only
can_restrict_members: bool = False,
can_promote_members: bool = False,
can_change_info: bool = False,
can_post_messages: bool = False, # Channels only
+ can_post_stories: bool = False, # Channels only
can_edit_messages: bool = False, # Channels only
+ can_edit_stories: bool = False, # Channels only
can_invite_users: bool = False,
can_pin_messages: bool = False, # Groups and supergroups only
can_manage_topics: bool = False, # Supergroups only
@@ -90,12 +105,15 @@ def __init__(
self.can_manage_chat: bool = can_manage_chat
self.can_delete_messages: bool = can_delete_messages
+ self.can_delete_stories: bool = can_delete_stories
self.can_manage_video_chats: bool = can_manage_video_chats
self.can_restrict_members: bool = can_restrict_members
self.can_promote_members: bool = can_promote_members
self.can_change_info: bool = can_change_info
self.can_post_messages: bool = can_post_messages
+ self.can_post_stories: bool = can_post_stories
self.can_edit_messages: bool = can_edit_messages
+ self.can_edit_stories: bool = can_edit_stories
self.can_invite_users: bool = can_invite_users
self.can_pin_messages: bool = can_pin_messages
self.can_manage_topics: bool = can_manage_topics
@@ -106,12 +124,15 @@ def _parse(admin_rights: "raw.base.ChatAdminRights") -> "ChatPrivileges":
return ChatPrivileges(
can_manage_chat=admin_rights.other,
can_delete_messages=admin_rights.delete_messages,
+ can_delete_stories=admin_rights.delete_stories,
can_manage_video_chats=admin_rights.manage_call,
can_restrict_members=admin_rights.ban_users,
can_promote_members=admin_rights.add_admins,
can_change_info=admin_rights.change_info,
can_post_messages=admin_rights.post_messages,
+ can_post_stories=admin_rights.post_stories,
can_edit_messages=admin_rights.edit_messages,
+ can_edit_stories=admin_rights.edit_stories,
can_invite_users=admin_rights.invite_users,
can_pin_messages=admin_rights.pin_messages,
can_manage_topics=admin_rights.manage_topics,
diff --git a/pyrogram/types/user_and_chats/folder.py b/pyrogram/types/user_and_chats/folder.py
new file mode 100644
index 000000000..60f2b0e2c
--- /dev/null
+++ b/pyrogram/types/user_and_chats/folder.py
@@ -0,0 +1,443 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 typing import List, Union
+
+import pyrogram
+from pyrogram import raw
+from pyrogram import types
+from pyrogram import utils
+from ..object import Object
+
+
+class Folder(Object):
+ """A user's folder.
+
+ Parameters:
+ id (``int``):
+ The folder id.
+
+ title (``str``):
+ The folder title.
+
+ included_chats (List of :obj:`~pyrogram.types.Chat`, *optional*):
+ A list of included chats in folder.
+
+ excluded_chats (List of :obj:`~pyrogram.types.Chat`, *optional*):
+ A list of excluded chats in folder.
+
+ pinned_chats (List of :obj:`~pyrogram.types.Chat`, *optional*):
+ A list of pinned chats in folder.
+
+ contacts (``bool``, *optional*):
+ True, if the folder includes contacts.
+
+ non_contacts (``bool``, *optional*):
+ True, if the folder includes non contacts.
+
+ groups (``bool``, *optional*):
+ True, if the folder includes groups.
+
+ channels (``bool``, *optional*):
+ True, if the folder includes channels.
+
+ bots (``bool``, *optional*):
+ True, if the folder includes bots.
+
+ exclude_muted (``bool``, *optional*):
+ True, if the folder exclude muted.
+
+ exclude_read (``bool``, *optional*):
+ True, if the folder exclude read.
+
+ exclude_archived (``bool``, *optional*):
+ True, if the folder exclude archived.
+
+ emoji (``str``, *optional*):
+ Folder emoji.
+ """
+
+ def __init__(
+ self,
+ *,
+ client: "pyrogram.Client" = None,
+ id: int,
+ title: str,
+ included_chats: List["types.Chat"] = None,
+ excluded_chats: List["types.Chat"] = None,
+ pinned_chats: List["types.Chat"] = None,
+ contacts: bool = None,
+ non_contacts: bool = None,
+ groups: bool = None,
+ channels: bool = None,
+ bots: bool = None,
+ exclude_muted: bool = None,
+ exclude_read: bool = None,
+ exclude_archived: bool = None,
+ emoji: str = None,
+ has_my_invites: bool = None
+ ):
+ super().__init__(client)
+
+ self.id = id
+ self.title = title
+ self.included_chats = included_chats
+ self.excluded_chats = excluded_chats
+ self.pinned_chats = pinned_chats
+ self.contacts = contacts
+ self.non_contacts = non_contacts
+ self.groups = groups
+ self.channels = channels
+ self.bots = bots
+ self.exclude_muted = exclude_muted
+ self.exclude_read = exclude_read
+ self.exclude_archived = exclude_archived
+ self.emoji = emoji
+ self.has_my_invites = has_my_invites
+
+ @staticmethod
+ def _parse(client, folder: "raw.types.DialogFilter", users, chats) -> "Folder":
+ included_chats = []
+ excluded_chats = []
+ pinned_chats = []
+
+ for peer in folder.include_peers:
+ try:
+ included_chats.append(types.Chat._parse_dialog(client, peer, users, chats))
+ except KeyError:
+ pass
+
+ if getattr(folder, "exclude_peers", None):
+ for peer in folder.exclude_peers:
+ try:
+ excluded_chats.append(types.Chat._parse_dialog(client, peer, users, chats))
+ except KeyError:
+ pass
+
+ for peer in folder.pinned_peers:
+ try:
+ pinned_chats.append(types.Chat._parse_dialog(client, peer, users, chats))
+ except KeyError:
+ pass
+
+ return Folder(
+ id=folder.id,
+ title=folder.title,
+ included_chats=types.List(included_chats) or None,
+ excluded_chats=types.List(excluded_chats) or None,
+ pinned_chats=types.List(pinned_chats) or None,
+ contacts=getattr(folder, "contacts", None),
+ non_contacts=getattr(folder, "non_contacts", None),
+ groups=getattr(folder, "groups", None),
+ channels=getattr(folder, "broadcasts", None),
+ bots=getattr(folder, "bots", None),
+ exclude_muted=getattr(folder, "exclude_muted", None),
+ exclude_read=getattr(folder, "exclude_read", None),
+ exclude_archived=getattr(folder, "exclude_archived", None),
+ emoji=folder.emoticon or None,
+ has_my_invites=getattr(folder, "has_my_invites", None),
+ client=client
+ )
+
+ async def delete(self):
+ """Bound method *delete* of :obj:`~pyrogram.types.Folder`.
+
+ Use as a shortcut for:
+
+ .. code-block:: python
+
+ await client.delete_folder(123456789)
+
+ Example:
+ .. code-block:: python
+
+ await folder.delete()
+
+ Returns:
+ True on success.
+ """
+
+ return await self._client.delete_folder(self.id)
+
+ async def update(
+ self,
+ included_chats: List[Union[int, str]] = None,
+ excluded_chats: List[Union[int, str]] = None,
+ pinned_chats: List[Union[int, str]] = None,
+ title: str = None,
+ contacts: bool = None,
+ non_contacts: bool = None,
+ groups: bool = None,
+ channels: bool = None,
+ bots: bool = None,
+ exclude_muted: bool = None,
+ exclude_read: bool = None,
+ exclude_archived: bool = None,
+ emoji: str = None
+ ):
+ """Bound method *update_peers* of :obj:`~pyrogram.types.Folder`.
+
+ Use as a shortcut for:
+
+ .. code-block:: python
+
+ await client.update_folder(
+ folder_id,
+ title="New folder",
+ included_chats=["me"]
+ )
+
+ Example:
+ .. code-block:: python
+
+ await folder.update(included_chats=["me"])
+
+ Parameters:
+ included_chats (``int`` | ``str`` | List of ``int`` or ``str``, *optional*):
+ Users or chats that should added in the folder
+ You can pass an ID (int), username (str) or phone number (str).
+ Multiple users can be added by passing a list of IDs, usernames or phone numbers.
+
+ excluded_chats (``int`` | ``str`` | List of ``int`` or ``str``, *optional*):
+ Users or chats that should excluded from the folder
+ You can pass an ID (int), username (str) or phone number (str).
+ Multiple users can be added by passing a list of IDs, usernames or phone numbers.
+
+ pinned_chats (``int`` | ``str`` | List of ``int`` or ``str``, *optional*):
+ Users or chats that should pinned in the folder
+ You can pass an ID (int), username (str) or phone number (str).
+ Multiple users can be added by passing a list of IDs, usernames or phone numbers.
+
+ title (``str``, *optional*):
+ A folder title was changed to this value.
+
+ contacts (``bool``, *optional*):
+ Pass True if folder should contain contacts.
+
+ non_contacts (``bool``, *optional*):
+ Pass True if folder should contain non contacts.
+
+ groups (``bool``, *optional*):
+ Pass True if folder should contain groups.
+
+ channels (``bool``, *optional*):
+ Pass True if folder should contain channels.
+
+ bots (``bool``, *optional*):
+ Pass True if folder should contain bots.
+
+ exclude_muted (``bool``, *optional*):
+ Pass True if folder should exclude muted users.
+
+ exclude_archived (``bool``, *optional*):
+ Pass True if folder should exclude archived users.
+
+ emoji (``str``, *optional*):
+ Folder emoji.
+ Pass None to leave the folder icon as default.
+
+ Returns:
+ True on success.
+ """
+ if not included_chats:
+ included_chats = [i.id for i in self.included_chats or []]
+
+ if not included_chats:
+ excluded_chats = [i.id for i in self.excluded_chats or []]
+
+ if not included_chats:
+ pinned_chats = [i.id for i in self.pinned_chats or []]
+
+ return await self._client.update_folder(
+ folder_id=self.id,
+ title=title or self.title,
+ included_chats=included_chats,
+ excluded_chats=excluded_chats,
+ pinned_chats=pinned_chats,
+ contacts=contacts or self.contacts,
+ non_contacts=non_contacts or self.non_contacts,
+ groups=groups or self.groups,
+ channels=channels or self.channels,
+ bots=bots or self.bots,
+ exclude_muted=exclude_muted or self.exclude_muted,
+ exclude_read=exclude_read or self.exclude_read,
+ exclude_archived=exclude_archived or self.exclude_archived,
+ emoji=emoji or self.emoji
+ )
+
+ async def include_chat(self, chat_id: Union[int, str]):
+ """Bound method *include_chat* of :obj:`~pyrogram.types.Folder`.
+
+ Use as a shortcut for:
+
+ .. code-block:: python
+
+ await client.update_folder(
+ folder_id=123456789,
+ included_chats=[chat_id],
+ excluded_chats=[...],
+ pinned_chats=[...]
+ )
+
+ Example:
+ .. code-block:: python
+
+ await folder.include_chat(chat_id)
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier for the target chat or username of the target user/channel/supergroup
+ (in the format @username).
+
+ Returns:
+ True on success.
+ """
+
+ return await self.update(
+ included_chats=[i.id for i in self.included_chats or []] + [chat_id],
+ excluded_chats=[i.id for i in self.excluded_chats or []],
+ pinned_chats=[i.id for i in self.pinned_chats or []]
+ )
+
+ async def exclude_chat(self, chat_id: Union[int, str]):
+ """Bound method *exclude_chat* of :obj:`~pyrogram.types.Folder`.
+
+ Use as a shortcut for:
+
+ .. code-block:: python
+
+ await client.update_folder(
+ folder_id=123456789,
+ included_chats=[...],
+ excluded_chats=[chat_id],
+ pinned_chats=[...]
+ )
+
+ Example:
+ .. code-block:: python
+
+ await folder.exclude_chat(chat_id)
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier for the target chat or username of the target user/channel/supergroup
+ (in the format @username).
+
+ Returns:
+ True on success.
+ """
+
+ return await self.update(
+ included_chats=[i.id for i in self.included_chats or []],
+ excluded_chats=[i.id for i in self.excluded_chats or []] + [chat_id],
+ pinned_chats=[i.id for i in self.pinned_chats or []],
+ )
+
+ async def pin_chat(self, chat_id: Union[int, str]):
+ """Bound method *pin_chat* of :obj:`~pyrogram.types.Folder`.
+
+ Use as a shortcut for:
+
+ .. code-block:: python
+
+ await client.update_folder(
+ folder_id=123456789,
+ included_chats=[chat_id],
+ excluded_chats=[chat_id],
+ pinned_chats=[...]
+ )
+
+ Example:
+ .. code-block:: python
+
+ await folder.pin_chat(chat_id)
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier for the target chat or username of the target user/channel/supergroup
+ (in the format @username).
+
+ Returns:
+ True on success.
+ """
+
+ return await self.update(
+ included_chats=[i.id for i in self.included_chats or []] + [chat_id],
+ excluded_chats=[i.id for i in self.excluded_chats or []],
+ pinned_chats=[i.id for i in self.pinned_chats or []] + [chat_id]
+ )
+
+ async def remove_chat(self, chat_id: Union[int, str]):
+ """Bound method *remove_chat* of :obj:`~pyrogram.types.Folder`.
+
+ Remove chat from included, excluded and pinned chats.
+
+ Use as a shortcut for:
+
+ .. code-block:: python
+
+ await client.update_folder(
+ folder_id=123456789,
+ included_chats=[...],
+ excluded_chats=[...],
+ pinned_chats=[...]
+ )
+
+ Example:
+ .. code-block:: python
+
+ await folder.remove_chat(chat_id)
+
+ Parameters:
+ chat_id (``int`` | ``str``):
+ Unique identifier for the target chat or username of the target user/channel/supergroup
+ (in the format @username).
+
+ Returns:
+ True on success.
+ """
+ peer = await self._client.resolve_peer(chat_id)
+ peer_id = utils.get_peer_id(peer)
+
+ return await self.update(
+ included_chats=[i.id for i in self.included_chats or [] if peer_id != i.id],
+ excluded_chats=[i.id for i in self.excluded_chats or [] if peer_id != i.id],
+ pinned_chats=[i.id for i in self.pinned_chats or [] if peer_id != i.id]
+ )
+
+ async def export_link(self):
+ """Bound method *export_link* of :obj:`~pyrogram.types.Folder`.
+
+ Use as a shortcut for:
+
+ .. code-block:: python
+
+ await client.export_folder_link(123456789)
+
+ Example:
+ .. code-block:: python
+
+ await folder.export_link()
+
+ Returns:
+ ``str``: On success, a link to the folder as string is returned.
+ """
+
+ return await self._client.export_folder_link(
+ folder_id=self.id
+ )
diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index 70745b983..a705fd849 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -100,13 +100,13 @@ class User(Object, Update):
is_premium (``bool``, *optional*):
True, if this user is a premium user.
- is_close_friend (``bool``):
+ is_close_friend (``bool``, *optional*):
True, if this user is a close friend.
- is_stories_hidden (``bool``):
+ is_stories_hidden (``bool``, *optional*):
True, if this user has hidden stories.
- is_stories_unavailable (``bool``):
+ is_stories_unavailable (``bool``, *optional*):
True, if this chat stories is unavailable.
first_name (``str``, *optional*):
@@ -164,11 +164,11 @@ class User(Object, Update):
``user.mention("another name")`` for a custom name. To choose a different style
("html" or "md"/"markdown") use ``user.mention(style="md")``.
- color (``int``, *optional*)
- Chat color.
+ reply_color (:obj:`~pyrogram.types.ChatColor`, *optional*)
+ Chat reply color.
- background_emoji_id (``int``, *optional*)
- Chat background emoji id.
+ profile_color (:obj:`~pyrogram.types.ChatColor`, *optional*)
+ Chat profile color.
"""
def __init__(
@@ -203,8 +203,8 @@ def __init__(
phone_number: str = None,
photo: "types.ChatPhoto" = None,
restrictions: List["types.Restriction"] = None,
- color: int = None,
- background_emoji_id: int = None
+ reply_color: "types.ChatColor" = None,
+ profile_color: "types.ChatColor" = None
):
super().__init__(client)
@@ -236,8 +236,8 @@ def __init__(
self.phone_number = phone_number
self.photo = photo
self.restrictions = restrictions
- self.color = color
- self.background_emoji_id = background_emoji_id
+ self.reply_color = reply_color
+ self.profile_color = profile_color
@property
def full_name(self) -> str:
@@ -275,7 +275,7 @@ def _parse(client, user: "raw.base.User") -> Optional["User"]:
first_name=user.first_name,
last_name=user.last_name,
**User._parse_status(user.status, user.bot),
- username=user.username,
+ username=user.username or (user.usernames[0].username if user.usernames else None),
usernames=types.List([types.Username._parse(r) for r in user.usernames]) or None,
language_code=user.lang_code,
emoji_status=types.EmojiStatus._parse(client, user.emoji_status),
@@ -283,8 +283,8 @@ def _parse(client, user: "raw.base.User") -> Optional["User"]:
phone_number=user.phone,
photo=types.ChatPhoto._parse(client, user.photo, user.id, user.access_hash),
restrictions=types.List([types.Restriction._parse(r) for r in user.restriction_reason]) or None,
- color=getattr(user, "color", None),
- background_emoji_id=getattr(user, "background_emoji_id", None),
+ reply_color=types.ChatColor._parse(getattr(user, "color", None)),
+ profile_color=types.ChatColor._parse_profile_color(getattr(user, "profile_color", None)),
client=client
)
diff --git a/pyrogram/utils.py b/pyrogram/utils.py
index 7c492facc..0bc9474b4 100644
--- a/pyrogram/utils.py
+++ b/pyrogram/utils.py
@@ -53,7 +53,8 @@ async def ainput(prompt: str = "", *, hide: bool = False):
def get_input_media_from_file_id(
file_id: str,
expected_file_type: FileType = None,
- ttl_seconds: int = None
+ ttl_seconds: int = None,
+ has_spoiler: bool = None
) -> Union["raw.types.InputMediaPhoto", "raw.types.InputMediaDocument"]:
try:
decoded = FileId.decode(file_id)
@@ -78,6 +79,7 @@ def get_input_media_from_file_id(
access_hash=decoded.access_hash,
file_reference=decoded.file_reference
),
+ spoiler=has_spoiler,
ttl_seconds=ttl_seconds
)
@@ -88,6 +90,7 @@ def get_input_media_from_file_id(
access_hash=decoded.access_hash,
file_reference=decoded.file_reference
),
+ spoiler=has_spoiler,
ttl_seconds=ttl_seconds
)
@@ -129,7 +132,7 @@ async def parse_messages(
# We need a chat id, but some messages might be empty (no chat attribute available)
# Scan until we find a message with a chat available (there must be one, because we are fetching replies)
for m in parsed_messages:
- if not isinstance(m, pyrogram.types.Message):
+ if not isinstance(m, types.Message):
continue
if m.chat:
@@ -138,13 +141,10 @@ async def parse_messages(
else:
chat_id = 0
- # whether all messages are inside of our current chat
- is_all_within_chat = True
- for current_message_id in messages_with_replies:
- if messages_with_replies[current_message_id].reply_to_peer_id:
- is_all_within_chat = False
- break
-
+ is_all_within_chat = not any(
+ value.reply_to_peer_id
+ for value in messages_with_replies.values()
+ )
reply_messages: List[pyrogram.types.Message] = []
if is_all_within_chat:
# fast path: fetch all messages within the same chat
@@ -155,12 +155,11 @@ async def parse_messages(
)
else:
# slow path: fetch all messages individually
- for current_message_id in messages_with_replies:
- target_reply_to = messages_with_replies[current_message_id]
+ for target_reply_to in messages_with_replies.values():
to_be_added_msg = None
the_chat_id = chat_id
if target_reply_to.reply_to_peer_id:
- the_chat_id = f"-100{target_reply_to.reply_to_peer_id.channel_id}"
+ the_chat_id = get_channel_id(target_reply_to.reply_to_peer_id.channel_id)
to_be_added_msg = await client.get_messages(
chat_id=the_chat_id,
message_ids=target_reply_to.reply_to_msg_id,
@@ -180,13 +179,12 @@ async def parse_messages(
reply_id = reply_to.reply_to_msg_id
for reply in reply_messages:
- if reply.id == reply_id:
- if not reply.forum_topic_created:
- message.reply_to_message = reply
+ if reply.id == reply_id and not reply.forum_topic_created:
+ message.reply_to_message = reply
if message_reply_to_story:
for m in parsed_messages:
- if not isinstance(m, pyrogram.types.Message):
+ if not isinstance(m, types.Message):
continue
if m.chat:
@@ -196,7 +194,7 @@ async def parse_messages(
chat_id = 0
reply_messages = {}
- for msg_id in message_reply_to_story.keys():
+ for msg_id in message_reply_to_story:
reply_messages[msg_id] = await client.get_stories(
message_reply_to_story[msg_id]['user_id'],
message_reply_to_story[msg_id]['story_id']
@@ -276,47 +274,34 @@ def unpack_inline_message_id(inline_message_id: str) -> "raw.base.InputBotInline
MIN_CHANNEL_ID = -1002147483647
MAX_CHANNEL_ID = -1000000000000
-MIN_CHAT_ID = -2147483647
+MIN_CHAT_ID = -999999999999
MAX_USER_ID_OLD = 2147483647
MAX_USER_ID = 999999999999
-def get_raw_peer_id(peer: raw.base.Peer) -> Optional[int]:
+def get_raw_peer_id(peer: Union[raw.base.Peer, raw.base.InputPeer]) -> Optional[int]:
"""Get the raw peer id from a Peer object"""
- if isinstance(peer, raw.types.PeerUser):
- return peer.user_id
-
- if isinstance(peer, raw.types.PeerChat):
- return peer.chat_id
-
- if isinstance(peer, raw.types.PeerChannel):
- return peer.channel_id
-
- return None
-
-def get_input_peer_id(peer: raw.base.InputPeer) -> Optional[int]:
- """Get the raw peer id from a InputPeer object"""
- if isinstance(peer, raw.types.InputPeerUser):
+ if isinstance(peer, (raw.types.PeerUser, raw.types.InputPeerUser)):
return peer.user_id
- if isinstance(peer, raw.types.InputPeerChat):
+ if isinstance(peer, (raw.types.PeerChat, raw.types.InputPeerChat)):
return peer.chat_id
- if isinstance(peer, raw.types.InputPeerChannel):
+ if isinstance(peer, (raw.types.PeerChannel, raw.types.InputPeerChannel)):
return peer.channel_id
return None
-def get_peer_id(peer: raw.base.Peer) -> int:
+def get_peer_id(peer: Union[raw.base.Peer, raw.base.InputPeer]) -> int:
"""Get the non-raw peer id from a Peer object"""
- if isinstance(peer, raw.types.PeerUser):
+ if isinstance(peer, (raw.types.PeerUser, raw.types.InputPeerUser)):
return peer.user_id
- if isinstance(peer, raw.types.PeerChat):
+ if isinstance(peer, (raw.types.PeerChat, raw.types.InputPeerChat)):
return -peer.chat_id
- if isinstance(peer, raw.types.PeerChannel):
+ if isinstance(peer, (raw.types.PeerChannel, raw.types.InputPeerChannel)):
return MAX_CHANNEL_ID - peer.channel_id
raise ValueError(f"Peer type invalid: {peer}")
@@ -334,12 +319,14 @@ def get_peer_type(peer_id: int) -> str:
raise ValueError(f"Peer id invalid: {peer_id}")
+
def get_reply_to(
reply_to_message_id: Optional[int] = None,
message_thread_id: Optional[int] = None,
reply_to_peer: Optional[raw.base.InputPeer] = None,
quote_text: Optional[str] = None,
quote_entities: Optional[List[raw.base.MessageEntity]] = None,
+ quote_offset: Optional[int] = None,
reply_to_story_id: Optional[int] = None
) -> Optional[Union[raw.types.InputReplyToMessage, raw.types.InputReplyToStory]]:
"""Get InputReply for reply_to argument"""
@@ -353,10 +340,12 @@ def get_reply_to(
reply_to_peer_id=reply_to_peer,
quote_text=quote_text,
quote_entities=quote_entities,
+ quote_offset=quote_offset,
)
return None
+
def get_channel_id(peer_id: int) -> int:
return MAX_CHANNEL_ID - peer_id
@@ -469,73 +458,17 @@ async def parse_text_entities(
"entities": entities
}
-async def parse_caption_entities(
- client: "pyrogram.Client",
- text: str,
- parse_mode: enums.ParseMode,
- entities: List["types.MessageEntity"]
-) -> Dict[str, Union[str, List[raw.base.MessageEntity]]]:
- if entities:
- # Inject the client instance because parsing user mentions requires it
- for entity in entities:
- entity._client = client
-
- text, entities = text, [await entity.write() for entity in entities] or None
- else:
- text, entities = (await client.parser.parse(text, parse_mode)).values()
-
- return {
- "caption": text,
- "entities": entities
- }
-
-async def parse_caption_entities(
- client: "pyrogram.Client",
- text: str,
- parse_mode: enums.ParseMode,
- entities: List["types.MessageEntity"]
-) -> Dict[str, Union[str, List[raw.base.MessageEntity]]]:
- if entities:
- # Inject the client instance because parsing user mentions requires it
- for entity in entities:
- entity._client = client
-
- text, entities = text, [await entity.write() for entity in entities] or None
- else:
- text, entities = (await client.parser.parse(text, parse_mode)).values()
-
- return {
- "caption": text,
- "entities": entities
- }
-
def zero_datetime() -> datetime:
return datetime.fromtimestamp(0, timezone.utc)
+def max_datetime() -> datetime:
+ return datetime.fromtimestamp((1 << 31) - 1, timezone.utc)
+
def timestamp_to_datetime(ts: Optional[int]) -> Optional[datetime]:
return datetime.fromtimestamp(ts) if ts else None
def datetime_to_timestamp(dt: Optional[datetime]) -> Optional[int]:
return int(dt.timestamp()) if dt else None
-
-
-def get_reply_head_fm(message_thread_id: int, reply_to_message_id: int) -> raw.types.InputReplyToMessage:
- reply_to = None
- if (
- reply_to_message_id or
- message_thread_id
- ):
- if not reply_to_message_id:
- reply_to = raw.types.InputReplyToMessage(
- reply_to_msg_id=message_thread_id,
- top_msg_id=message_thread_id
- )
- else:
- reply_to = raw.types.InputReplyToMessage(
- reply_to_msg_id=reply_to_message_id,
- top_msg_id=message_thread_id
- )
- return reply_to
diff --git a/tests/parser/test_html.py b/tests/parser/test_html.py
index b9138f3cf..5f1c4fd89 100644
--- a/tests/parser/test_html.py
+++ b/tests/parser/test_html.py
@@ -100,6 +100,19 @@ def test_html_unparse_pre():
assert HTML.unparse(text=text, entities=entities) == expected
+def test_html_unparse_blockquote():
+ expected = """Quote text
+ from pyrogram"""
+
+ text = """Quote text
+ from pyrogram"""
+
+ entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BLOCKQUOTE, offset=0,
+ length=10)])
+
+ assert HTML.unparse(text=text, entities=entities) == expected
+
+
def test_html_unparse_mixed():
expected = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd" \
"eeeeeeeeeeffffffffffggggggg
ggghhhhhhhhhh"
diff --git a/tests/parser/test_markdown.py b/tests/parser/test_markdown.py
new file mode 100644
index 000000000..81aaacb33
--- /dev/null
+++ b/tests/parser/test_markdown.py
@@ -0,0 +1,115 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-present Dan
+#
+# 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 pyrogram.parser.markdown import Markdown
+
+
+# expected: the expected unparsed Markdown
+# text: original text without entities
+# entities: message entities coming from the server
+
+def test_markdown_unparse_bold():
+ expected = "**bold**"
+ text = "bold"
+ entities = pyrogram.types.List(
+ [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=4)])
+
+ assert Markdown.unparse(text=text, entities=entities) == expected
+
+
+def test_markdown_unparse_italic():
+ expected = "__italic__"
+ text = "italic"
+ entities = pyrogram.types.List(
+ [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=0, length=6)])
+
+ assert Markdown.unparse(text=text, entities=entities) == expected
+
+
+def test_markdown_unparse_strike():
+ expected = "~~strike~~"
+ text = "strike"
+ entities = pyrogram.types.List(
+ [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=0, length=6)])
+
+ assert Markdown.unparse(text=text, entities=entities) == expected
+
+
+def test_markdown_unparse_spoiler():
+ expected = "||spoiler||"
+ text = "spoiler"
+ entities = pyrogram.types.List(
+ [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=0, length=7)])
+
+ assert Markdown.unparse(text=text, entities=entities) == expected
+
+
+def test_markdown_unparse_url():
+ expected = '[URL](https://pyrogram.org/)'
+ text = "URL"
+ entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.TEXT_LINK,
+ offset=0, length=3, url='https://pyrogram.org/')])
+
+ assert Markdown.unparse(text=text, entities=entities) == expected
+
+
+def test_markdown_unparse_code():
+ expected = '`code`'
+ text = "code"
+ entities = pyrogram.types.List(
+ [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=0, length=4)])
+
+ assert Markdown.unparse(text=text, entities=entities) == expected
+
+
+def test_markdown_unparse_pre():
+ expected = """```python
+for i in range(10):
+ print(i)
+```"""
+
+ text = """for i in range(10):
+ print(i)"""
+
+ entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.PRE, offset=0,
+ length=32, language='python')])
+
+ assert Markdown.unparse(text=text, entities=entities) == expected
+
+
+def test_markdown_unparse_mixed():
+ expected = "**aaaaaaa__aaabbb**__~~dddddddd||ddeee~~||||eeeeeeefff||ffff`fffggggggg`ggghhhhhhhhhh"
+ text = "aaaaaaaaaabbbddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhh"
+ entities = pyrogram.types.List(
+ [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=13),
+ pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=7, length=6),
+ pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=13, length=13),
+ pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=21, length=5),
+ pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=26, length=10),
+ pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=40, length=10)])
+
+ assert Markdown.unparse(text=text, entities=entities) == expected
+
+
+def test_markdown_unparse_no_entities():
+ expected = "text"
+ text = "text"
+ entities = []
+
+ assert Markdown.unparse(text=text, entities=entities) == expected