Compare commits

...

8 commits

Author SHA1 Message Date
wulan17
b0a8c9ce7f
pyrofork: Refactor Giveaway
Some checks are pending
Build-docs / build (push) Waiting to run
Pyrofork / build (macos-latest, 3.10) (push) Waiting to run
Pyrofork / build (macos-latest, 3.11) (push) Waiting to run
Pyrofork / build (macos-latest, 3.12) (push) Waiting to run
Pyrofork / build (macos-latest, 3.13) (push) Waiting to run
Pyrofork / build (macos-latest, 3.9) (push) Waiting to run
Pyrofork / build (ubuntu-latest, 3.10) (push) Waiting to run
Pyrofork / build (ubuntu-latest, 3.11) (push) Waiting to run
Pyrofork / build (ubuntu-latest, 3.12) (push) Waiting to run
Pyrofork / build (ubuntu-latest, 3.13) (push) Waiting to run
Pyrofork / build (ubuntu-latest, 3.9) (push) Waiting to run
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-25 22:57:59 +07:00
wulan17
bce191c3fd
pyrofork: utils: Don't append to messages to messages_with_replies if reply_to_msg_id is None
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-25 22:31:14 +07:00
wulan17
dc05f2b791
Revert "Pyrofork: types: message: Fix cross chat reply parsing"
This reverts commit b4cb8ff17c.

Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-25 22:23:01 +07:00
KurimuzonAkuma
d7e13e7b18
Add ExternalReplyInfo
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-25 21:13:41 +07:00
KurimuzonAkuma
5b9081cb08
Add LinkPreviewOptions
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-25 21:13:36 +07:00
KurimuzonAkuma
98dab35e93
Add MessageOriginImport
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-25 21:00:39 +07:00
KurimuzonAkuma
fed146f54f
Refactor Message
- Refactor Message.quote
- Add forward_origin

Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-25 21:00:31 +07:00
KurimuzonAkuma
125f49ec7f
Make url parameter optional for send_web_page
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-25 21:00:24 +07:00
25 changed files with 1191 additions and 206 deletions

View file

@ -513,17 +513,25 @@ def pyrogram_api():
Messages & Media Messages & Media
Message Message
MessageEntity MessageEntity
MessageOriginChannel
MessageOriginChat
MessageOriginHiddenUser
MessageOriginImport
MessageOriginUser
MessageOrigin
Photo Photo
Thumbnail Thumbnail
Audio Audio
AvailableEffect AvailableEffect
Document Document
ExternalReplyInfo
AlternativeVideo AlternativeVideo
Animation Animation
Video Video
Voice Voice
VideoNote VideoNote
Contact Contact
LinkPreviewOptions
Location Location
Venue Venue
Sticker Sticker
@ -540,6 +548,7 @@ def pyrogram_api():
WebPagePreview WebPagePreview
TranscribedAudio TranscribedAudio
TranslatedText TranslatedText
TextQuote
Poll Poll
PollOption PollOption
Dice Dice

View file

@ -30,6 +30,7 @@ from .gift_attribute_type import GiftAttributeType
from .listerner_types import ListenerTypes from .listerner_types import ListenerTypes
from .message_entity_type import MessageEntityType from .message_entity_type import MessageEntityType
from .message_media_type import MessageMediaType from .message_media_type import MessageMediaType
from .message_origin_type import MessageOriginType
from .message_service_type import MessageServiceType from .message_service_type import MessageServiceType
from .messages_filter import MessagesFilter from .messages_filter import MessagesFilter
from .next_code_type import NextCodeType from .next_code_type import NextCodeType
@ -57,6 +58,7 @@ __all__ = [
'ListenerTypes', 'ListenerTypes',
'MessageEntityType', 'MessageEntityType',
'MessageMediaType', 'MessageMediaType',
'MessageOriginType',
'MessageServiceType', 'MessageServiceType',
'MessagesFilter', 'MessagesFilter',
'NextCodeType', 'NextCodeType',

View file

@ -0,0 +1,41 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from enum import auto
from .auto_name import AutoName
class MessageOriginType(AutoName):
"""Message origin type enumeration used in :obj:`~pyrogram.types.MessageOrigin`."""
CHANNEL = auto()
"The message was originally a post in a channel"
CHAT = auto()
"The message was originally sent on behalf of a chat"
HIDDEN_USER = auto()
"The message was originally sent by a user, which is hidden by their privacy settings"
IMPORT = auto()
"The message was imported from a foreign chat service"
USER = auto()
"The message was originally sent by a known user"

View file

@ -17,14 +17,15 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>. # along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import logging
from typing import Optional from typing import Optional
import pyrogram import pyrogram
from pyrogram import raw, enums from pyrogram import enums, raw, types, utils
from pyrogram import types
from pyrogram import utils
from .inline_session import get_session from .inline_session import get_session
log = logging.getLogger(__name__)
class EditInlineText: class EditInlineText:
async def edit_inline_text( async def edit_inline_text(
@ -32,6 +33,7 @@ class EditInlineText:
inline_message_id: str, inline_message_id: str,
text: str, text: str,
parse_mode: Optional["enums.ParseMode"] = None, parse_mode: Optional["enums.ParseMode"] = None,
link_preview_options: "types.LinkPreviewOptions" = None,
disable_web_page_preview: bool = None, disable_web_page_preview: bool = None,
reply_markup: "types.InlineKeyboardMarkup" = None, reply_markup: "types.InlineKeyboardMarkup" = None,
invert_media: bool = None invert_media: bool = None
@ -51,6 +53,9 @@ class EditInlineText:
By default, texts are parsed using both Markdown and HTML styles. By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together. You can combine both syntaxes together.
link_preview_options (:obj:`~pyrogram.types.LinkPreviewOptions`, *optional*):
Options used for link preview generation for the message.
disable_web_page_preview (``bool``, *optional*): disable_web_page_preview (``bool``, *optional*):
Disables link previews for links in this message. Disables link previews for links in this message.
@ -77,6 +82,11 @@ class EditInlineText:
inline_message_id, message.text, inline_message_id, message.text,
disable_web_page_preview=True) disable_web_page_preview=True)
""" """
if disable_web_page_preview is not None:
log.warning(
"`disable_web_page_preview` is deprecated and will be removed in future updates. Use `link_preview_options` instead."
)
link_preview_options = types.LinkPreviewOptions(is_disabled=disable_web_page_preview)
unpacked = utils.unpack_inline_message_id(inline_message_id) unpacked = utils.unpack_inline_message_id(inline_message_id)
dc_id = unpacked.dc_id dc_id = unpacked.dc_id
@ -86,7 +96,7 @@ class EditInlineText:
return await session.invoke( return await session.invoke(
raw.functions.messages.EditInlineBotMessage( raw.functions.messages.EditInlineBotMessage(
id=unpacked, id=unpacked,
no_webpage=disable_web_page_preview or None, no_webpage=getattr(link_preview_options, "is_disabled", None) or None,
reply_markup=await reply_markup.write(self) if reply_markup else None, reply_markup=await reply_markup.write(self) if reply_markup else None,
**await self.parser.parse(text, parse_mode), **await self.parser.parse(text, parse_mode),
invert_media=invert_media invert_media=invert_media

View file

@ -17,12 +17,13 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>. # along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import logging
from typing import Union, List, Optional from typing import Union, List, Optional
import pyrogram import pyrogram
from pyrogram import raw, enums from pyrogram import enums, raw, types, utils
from pyrogram import types
from pyrogram import utils log = logging.getLogger(__name__)
class EditMessageText: class EditMessageText:
@ -33,6 +34,7 @@ class EditMessageText:
text: str, text: str,
parse_mode: Optional["enums.ParseMode"] = None, parse_mode: Optional["enums.ParseMode"] = None,
entities: List["types.MessageEntity"] = None, entities: List["types.MessageEntity"] = None,
link_preview_options: "types.LinkPreviewOptions" = None,
disable_web_page_preview: bool = None, disable_web_page_preview: bool = None,
invert_media: bool = None, invert_media: bool = None,
reply_markup: "types.InlineKeyboardMarkup" = None, reply_markup: "types.InlineKeyboardMarkup" = None,
@ -62,8 +64,8 @@ class EditMessageText:
entities (List of :obj:`~pyrogram.types.MessageEntity`): entities (List of :obj:`~pyrogram.types.MessageEntity`):
List of special entities that appear in message text, which can be specified instead of *parse_mode*. List of special entities that appear in message text, which can be specified instead of *parse_mode*.
disable_web_page_preview (``bool``, *optional*): link_preview_options (:obj:`~pyrogram.types.LinkPreviewOptions`, *optional*):
Disables link previews for links in this message. Options used for link preview generation for the message.
invert_media (``bool``, *optional*): invert_media (``bool``, *optional*):
Inverts the position of the media and caption. Inverts the position of the media and caption.
@ -89,11 +91,16 @@ class EditMessageText:
chat_id, message_id, message.text, chat_id, message_id, message.text,
disable_web_page_preview=True) disable_web_page_preview=True)
""" """
if disable_web_page_preview is not None:
log.warning(
"`disable_web_page_preview` is deprecated and will be removed in future updates. Use `link_preview_options` instead."
)
link_preview_options = types.LinkPreviewOptions(is_disabled=disable_web_page_preview)
rpc = raw.functions.messages.EditMessage( rpc = raw.functions.messages.EditMessage(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
id=message_id, id=message_id,
no_webpage=disable_web_page_preview or None, no_webpage=getattr(link_preview_options, "is_disabled", None) or None,
invert_media=invert_media, invert_media=invert_media,
reply_markup=await reply_markup.write(self) if reply_markup else None, reply_markup=await reply_markup.write(self) if reply_markup else None,
**await utils.parse_text_entities(self, text, parse_mode, entities) **await utils.parse_text_entities(self, text, parse_mode, entities)

View file

@ -17,12 +17,14 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>. # along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import logging
from datetime import datetime from datetime import datetime
from typing import Union, List, Optional from typing import List, Optional, Union
import pyrogram import pyrogram
from pyrogram import raw, utils, enums from pyrogram import enums, raw, types, utils
from pyrogram import types
log = logging.getLogger(__name__)
class SendMessage: class SendMessage:
@ -32,7 +34,7 @@ class SendMessage:
text: str, text: str,
parse_mode: Optional["enums.ParseMode"] = None, parse_mode: Optional["enums.ParseMode"] = None,
entities: List["types.MessageEntity"] = None, entities: List["types.MessageEntity"] = None,
disable_web_page_preview: bool = None, link_preview_options: "types.LinkPreviewOptions" = None,
disable_notification: bool = None, disable_notification: bool = None,
message_thread_id: int = None, message_thread_id: int = None,
business_connection_id: str = None, business_connection_id: str = None,
@ -51,7 +53,8 @@ class SendMessage:
"types.ReplyKeyboardMarkup", "types.ReplyKeyboardMarkup",
"types.ReplyKeyboardRemove", "types.ReplyKeyboardRemove",
"types.ForceReply" "types.ForceReply"
] = None ] = None,
disable_web_page_preview: bool = None, # TODO: Remove later
) -> "types.Message": ) -> "types.Message":
"""Send text messages. """Send text messages.
@ -74,8 +77,8 @@ class SendMessage:
entities (List of :obj:`~pyrogram.types.MessageEntity`): entities (List of :obj:`~pyrogram.types.MessageEntity`):
List of special entities that appear in message text, which can be specified instead of *parse_mode*. List of special entities that appear in message text, which can be specified instead of *parse_mode*.
disable_web_page_preview (``bool``, *optional*): link_preview_options (:obj:`~pyrogram.types.LinkPreviewOptions`, *optional*):
Disables link previews for links in this message. Options used for link preview generation for the message.
disable_notification (``bool``, *optional*): disable_notification (``bool``, *optional*):
Sends the message silently. Sends the message silently.
@ -167,6 +170,16 @@ class SendMessage:
message, entities = (await utils.parse_text_entities(self, text, parse_mode, entities)).values() message, entities = (await utils.parse_text_entities(self, text, parse_mode, entities)).values()
if disable_web_page_preview is not None or invert_media is not None:
if disable_web_page_preview is not None:
log.warning(
"`disable_web_page_preview` is deprecated and will be removed in future updates. Use `link_preview_options` instead."
)
link_preview_options = types.LinkPreviewOptions(
is_disabled=disable_web_page_preview,
invert_media=invert_media
)
reply_to = await utils.get_reply_to( reply_to = await utils.get_reply_to(
client=self, client=self,
chat_id=chat_id, chat_id=chat_id,
@ -181,7 +194,7 @@ class SendMessage:
rpc = raw.functions.messages.SendMessage( rpc = raw.functions.messages.SendMessage(
peer=await self.resolve_peer(chat_id), peer=await self.resolve_peer(chat_id),
no_webpage=disable_web_page_preview or None, no_webpage=getattr(link_preview_options, "is_disabled", None) or None,
silent=disable_notification or None, silent=disable_notification or None,
reply_to=reply_to, reply_to=reply_to,
random_id=self.rnd_id(), random_id=self.rnd_id(),
@ -191,7 +204,7 @@ class SendMessage:
entities=entities, entities=entities,
noforwards=protect_content, noforwards=protect_content,
allow_paid_floodskip=allow_paid_broadcast, allow_paid_floodskip=allow_paid_broadcast,
invert_media=invert_media, invert_media=getattr(link_preview_options, "invert_media", None) or None,
effect=message_effect_id, effect=message_effect_id,
) )
if business_connection_id is not None: if business_connection_id is not None:

View file

@ -28,7 +28,7 @@ class SendWebPage:
async def send_web_page( async def send_web_page(
self: "pyrogram.Client", self: "pyrogram.Client",
chat_id: Union[int, str], chat_id: Union[int, str],
url: str, url: str = None,
text: str = "", text: str = "",
parse_mode: Optional["enums.ParseMode"] = None, parse_mode: Optional["enums.ParseMode"] = None,
entities: List["types.MessageEntity"] = None, entities: List["types.MessageEntity"] = None,
@ -53,7 +53,7 @@ class SendWebPage:
"types.ForceReply" "types.ForceReply"
] = None ] = None
) -> "types.Message": ) -> "types.Message":
"""Send text Web Page Preview. """Send Web Page Preview.
.. include:: /_includes/usable-by/users-bots.rst .. include:: /_includes/usable-by/users-bots.rst
@ -64,12 +64,13 @@ class SendWebPage:
For a contact that exists in your Telegram address book you can use his phone number (str). For a contact that exists in your Telegram address book you can use his phone number (str).
You can also use chat public link in form of *t.me/<username>* (str). You can also use chat public link in form of *t.me/<username>* (str).
url (``str``):
Link that will be previewed.
text (``str``, *optional*): text (``str``, *optional*):
Text of the message to be sent. Text of the message to be sent.
url (``str``, *optional*):
Link that will be previewed.
If url not specified, the first URL found in the text will be used.
parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
By default, texts are parsed using both Markdown and HTML styles. By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together. You can combine both syntaxes together.
@ -131,7 +132,7 @@ class SendWebPage:
instructions to remove reply keyboard or to force a reply from the user. instructions to remove reply keyboard or to force a reply from the user.
Returns: Returns:
:obj:`~pyrogram.types.Message`: On success, the sent text message is returned. :obj:`~pyrogram.types.Message`: On success, the sent message is returned.
Example: Example:
.. code-block:: python .. code-block:: python
@ -141,6 +142,18 @@ class SendWebPage:
""" """
message, entities = (await utils.parse_text_entities(self, text, parse_mode, entities)).values() message, entities = (await utils.parse_text_entities(self, text, parse_mode, entities)).values()
if not url:
if entities:
for entity in entities:
if isinstance(entity, enums.MessageEntityType.URL):
url = entity.url
break
if not url:
url = utils.get_first_url(message)
if not url:
raise ValueError("URL not specified")
reply_to = await utils.get_reply_to( reply_to = await utils.get_reply_to(
client=self, client=self,

View file

@ -17,15 +17,18 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>. # along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from typing import Union, List, Match, Optional import logging
from typing import List, Match, Optional, Union
import pyrogram import pyrogram
from pyrogram import raw, enums from pyrogram import raw, enums
from pyrogram import types from pyrogram import types
from ... import utils
from ..object import Object from ..object import Object
from ..update import Update from ..update import Update
from ... import utils
log = logging.getLogger(__name__)
class CallbackQuery(Object, Update): class CallbackQuery(Object, Update):
"""An incoming callback query from a callback button in an inline keyboard. """An incoming callback query from a callback button in an inline keyboard.
@ -180,8 +183,9 @@ class CallbackQuery(Object, Update):
self, self,
text: str, text: str,
parse_mode: Optional["enums.ParseMode"] = None, parse_mode: Optional["enums.ParseMode"] = None,
disable_web_page_preview: bool = None, link_preview_options: "types.LinkPreviewOptions" = None,
reply_markup: "types.InlineKeyboardMarkup" = None, reply_markup: "types.InlineKeyboardMarkup" = None,
disable_web_page_preview: bool = None,
business_connection_id: Optional[str] = None business_connection_id: Optional[str] = None
) -> Union["types.Message", bool]: ) -> Union["types.Message", bool]:
"""Edit the text of messages attached to callback queries. """Edit the text of messages attached to callback queries.
@ -196,8 +200,8 @@ class CallbackQuery(Object, Update):
By default, texts are parsed using both Markdown and HTML styles. By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together. You can combine both syntaxes together.
disable_web_page_preview (``bool``, *optional*): link_preview_options (:obj:`~pyrogram.types.LinkPreviewOptions`, *optional*):
Disables link previews for links in this message. Options used for link preview generation for the message.
reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object. An InlineKeyboardMarkup object.
@ -213,6 +217,12 @@ class CallbackQuery(Object, Update):
Raises: Raises:
RPCError: In case of a Telegram RPC error. RPCError: In case of a Telegram RPC error.
""" """
if disable_web_page_preview is not None:
log.warning(
"`disable_web_page_preview` is deprecated and will be removed in future updates. Use `link_preview_options` instead."
)
link_preview_options = types.LinkPreviewOptions(is_disabled=disable_web_page_preview)
if self.inline_message_id is None: if self.inline_message_id is None:
return await self._client.edit_message_text( return await self._client.edit_message_text(
chat_id=self.message.chat.id, chat_id=self.message.chat.id,
@ -220,6 +230,7 @@ class CallbackQuery(Object, Update):
text=text, text=text,
parse_mode=parse_mode, parse_mode=parse_mode,
disable_web_page_preview=disable_web_page_preview, disable_web_page_preview=disable_web_page_preview,
link_preview_options=link_preview_options,
reply_markup=reply_markup, reply_markup=reply_markup,
business_connection_id=getattr(self.message, "business_connection_id", None) business_connection_id=getattr(self.message, "business_connection_id", None)
) )
@ -228,7 +239,7 @@ class CallbackQuery(Object, Update):
inline_message_id=self.inline_message_id, inline_message_id=self.inline_message_id,
text=text, text=text,
parse_mode=parse_mode, parse_mode=parse_mode,
disable_web_page_preview=disable_web_page_preview, link_preview_options=link_preview_options,
reply_markup=reply_markup reply_markup=reply_markup
) )

View file

@ -17,12 +17,15 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>. # along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional, List import logging
from typing import List, Optional
import pyrogram import pyrogram
from pyrogram import raw, types, utils, enums from pyrogram import enums, raw, types, utils
from .input_message_content import InputMessageContent from .input_message_content import InputMessageContent
log = logging.getLogger(__name__)
class InputTextMessageContent(InputMessageContent): class InputTextMessageContent(InputMessageContent):
"""Content of a text message to be sent as the result of an inline query. """Content of a text message to be sent as the result of an inline query.
@ -38,8 +41,8 @@ class InputTextMessageContent(InputMessageContent):
entities (List of :obj:`~pyrogram.types.MessageEntity`): entities (List of :obj:`~pyrogram.types.MessageEntity`):
List of special entities that appear in message text, which can be specified instead of *parse_mode*. List of special entities that appear in message text, which can be specified instead of *parse_mode*.
disable_web_page_preview (``bool``, *optional*): link_preview_options (:obj:`~pyrogram.types.LinkPreviewOptions`, *optional*):
Disables link previews for links in this message. Options used for link preview generation for the message.
""" """
def __init__( def __init__(
@ -47,13 +50,21 @@ class InputTextMessageContent(InputMessageContent):
message_text: str, message_text: str,
parse_mode: Optional["enums.ParseMode"] = None, parse_mode: Optional["enums.ParseMode"] = None,
entities: List["types.MessageEntity"] = None, entities: List["types.MessageEntity"] = None,
link_preview_options: "types.LinkPreviewOptions" = None,
disable_web_page_preview: bool = None disable_web_page_preview: bool = None
): ):
super().__init__() super().__init__()
if disable_web_page_preview is not None:
log.warning(
"`disable_web_page_preview` is deprecated and will be removed in future updates. Use `link_preview_options` instead."
)
link_preview_options = types.LinkPreviewOptions(is_disabled=disable_web_page_preview)
self.message_text = message_text self.message_text = message_text
self.parse_mode = parse_mode self.parse_mode = parse_mode
self.entities = entities self.entities = entities
self.link_preview_options = link_preview_options
self.disable_web_page_preview = disable_web_page_preview self.disable_web_page_preview = disable_web_page_preview
async def write(self, client: "pyrogram.Client", reply_markup): async def write(self, client: "pyrogram.Client", reply_markup):
@ -62,7 +73,7 @@ class InputTextMessageContent(InputMessageContent):
)).values() )).values()
return raw.types.InputBotInlineMessageText( return raw.types.InputBotInlineMessageText(
no_webpage=self.disable_web_page_preview or None, no_webpage=getattr(self.link_preview_options, "is_disabled", None) or None,
reply_markup=await reply_markup.write(client) if reply_markup else None, reply_markup=await reply_markup.write(client) if reply_markup else None,
message=message, message=message,
entities=entities entities=entities

View file

@ -27,16 +27,24 @@ from .contact import Contact
from .contact_registered import ContactRegistered from .contact_registered import ContactRegistered
from .dice import Dice from .dice import Dice
from .document import Document from .document import Document
from .external_reply_info import ExternalReplyInfo
from .game import Game from .game import Game
from .giveaway import Giveaway from .giveaway import Giveaway
from .giveaway_launched import GiveawayLaunched from .giveaway_launched import GiveawayLaunched
from .giveaway_result import GiveawayResult from .giveaway_result import GiveawayResult
from .link_preview_options import LinkPreviewOptions
from .location import Location from .location import Location
from .media_area import MediaArea from .media_area import MediaArea
from .media_area_channel_post import MediaAreaChannelPost from .media_area_channel_post import MediaAreaChannelPost
from .media_area_coordinates import MediaAreaCoordinates from .media_area_coordinates import MediaAreaCoordinates
from .message import Message from .message import Message
from .message_entity import MessageEntity from .message_entity import MessageEntity
from .message_origin import MessageOrigin
from .message_origin_channel import MessageOriginChannel
from .message_origin_chat import MessageOriginChat
from .message_origin_hidden_user import MessageOriginHiddenUser
from .message_origin_import import MessageOriginImport
from .message_origin_user import MessageOriginUser
from .photo import Photo from .photo import Photo
from .poll import Poll from .poll import Poll
from .poll_option import PollOption from .poll_option import PollOption
@ -71,6 +79,7 @@ from .wallpaper import Wallpaper
from .wallpaper_settings import WallpaperSettings from .wallpaper_settings import WallpaperSettings
from .transcribed_audio import TranscribedAudio from .transcribed_audio import TranscribedAudio
from .translated_text import TranslatedText from .translated_text import TranslatedText
from .text_quote import TextQuote
__all__ = [ __all__ = [
"AlternativeVideo", "AlternativeVideo",
@ -82,16 +91,24 @@ __all__ = [
"Contact", "Contact",
"ContactRegistered", "ContactRegistered",
"Document", "Document",
"ExternalReplyInfo",
"Game", "Game",
"Giveaway", "Giveaway",
"GiveawayLaunched", "GiveawayLaunched",
"GiveawayResult", "GiveawayResult",
"LinkPreviewOptions",
"Location", "Location",
"MediaArea", "MediaArea",
"MediaAreaChannelPost", "MediaAreaChannelPost",
"MediaAreaCoordinates", "MediaAreaCoordinates",
"Message", "Message",
"MessageEntity", "MessageEntity",
"MessageOrigin",
"MessageOriginChannel",
"MessageOriginChat",
"MessageOriginHiddenUser",
"MessageOriginImport",
"MessageOriginUser",
"Photo", "Photo",
"Thumbnail", "Thumbnail",
"StrippedThumbnail", "StrippedThumbnail",
@ -126,5 +143,6 @@ __all__ = [
"Wallpaper", "Wallpaper",
"WallpaperSettings", "WallpaperSettings",
"TranscribedAudio", "TranscribedAudio",
"TranslatedText" "TranslatedText",
"TextQuote"
] ]

View file

@ -0,0 +1,324 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Dict, Optional
import pyrogram
from pyrogram import enums, raw, types, utils
from ..object import Object
class ExternalReplyInfo(Object):
"""This object contains information about a message that is being replied to, which may come from another chat or forum topic.
Parameters:
origin (:obj:`~pyrogram.types.MessageOrigin`, *optional*):
Origin of the message replied to by the given message.
chat (:obj:`~pyrogram.types.Chat`, *optional*):
Chat the original message belongs to.
Available only if the chat is a supergroup or a channel.
message_id (``int``, *optional*):
Unique message identifier inside the original chat.
Available only if the original chat is a supergroup or a channel.
link_preview_options (:obj:`~pyrogram.types.LinkPreviewOptions`, *optional*):
Options used for link preview generation for the original message, if it is a text message.
media (:obj:`~pyrogram.enums.MessageMediaType`, *optional*):
The message is a media message.
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.
animation (:obj:`~pyrogram.types.Animation`, *optional*):
Message is an animation, information about the animation.
audio (:obj:`~pyrogram.types.Audio`, *optional*):
Message is an audio file, information about the file.
document (:obj:`~pyrogram.types.Document`, *optional*):
Message is a general file, information about the file.
paid_media (:obj:`~pyrogram.types.PaidMediaInfo`, *optional*):
Message contains paid media; information about the paid media.
photo (:obj:`~pyrogram.types.Photo`, *optional*):
Message is a photo, information about the photo.
sticker (:obj:`~pyrogram.types.Sticker`, *optional*):
Message is a sticker, information about the sticker.
story (:obj:`~pyrogram.types.Story`, *optional*):
Message is a forwarded story.
video (:obj:`~pyrogram.types.Video`, *optional*):
Message is a video, information about the video.
video_note (:obj:`~pyrogram.types.VideoNote`, *optional*):
Message is a video note, information about the video message.
voice (:obj:`~pyrogram.types.Voice`, *optional*):
Message is a voice message, information about the file.
has_media_spoiler (``bool``, *optional*):
True, if the message media is covered by a spoiler animation.
contact (:obj:`~pyrogram.types.Contact`, *optional*):
Message is a shared contact, information about the contact.
dice (:obj:`~pyrogram.types.Dice`, *optional*):
A dice containing a value that is randomly generated by Telegram.
game (:obj:`~pyrogram.types.Game`, *optional*):
Message is a game, information about the game.
giveaway (:obj:`~pyrogram.types.Giveaway`, *optional*):
Message is a scheduled giveaway, information about the giveaway.
giveaway_winners (:obj:`~pyrogram.types.GiveawayWinners`, *optional*):
A giveaway with public winners was completed
invoice (:obj:`~pyrogram.types.Invoice`, *optional*):
Message is a invoice, information about the invoice.
`More about payments » <https://core.telegram.org/bots/api#payments>`_
location (:obj:`~pyrogram.types.Location`, *optional*):
Message is a shared location, information about the location.
poll (:obj:`~pyrogram.types.Poll`, *optional*):
Message is a native poll, information about the poll.
venue (:obj:`~pyrogram.types.Venue`, *optional*):
Message is a venue, information about the venue.
"""
def __init__(
self,
*,
client: "pyrogram.Client" = None,
origin: "types.MessageOrigin" = None,
chat: "types.Chat" = None,
message_id: int,
link_preview_options: Optional["types.LinkPreviewOptions"] = None,
media: Optional["enums.MessageMediaType"] = None,
animation: Optional["types.Animation"] = None,
audio: Optional["types.Audio"] = None,
document: Optional["types.Document"] = None,
paid_media: Optional["types.PaidMediaInfo"] = None,
photo: Optional["types.Photo"] = None,
sticker: Optional["types.Sticker"] = None,
story: Optional["types.Story"] = None,
video: Optional["types.Video"] = None,
video_note: Optional["types.VideoNote"] = None,
voice: Optional["types.Voice"] = None,
has_media_spoiler: Optional[bool] = None,
contact: Optional["types.Contact"] = None,
dice: Optional["types.Dice"] = None,
game: Optional["types.Game"] = None,
giveaway: Optional["types.Giveaway"] = None,
giveaway_winners: Optional["types.GiveawayWinners"] = None,
invoice: Optional["types.Invoice"] = None,
location: Optional["types.Location"] = None,
poll: Optional["types.Poll"] = None,
venue: Optional["types.Venue"] = None,
):
super().__init__(client)
self.origin = origin
self.chat = chat
self.message_id = message_id
self.link_preview_options = link_preview_options
self.media = media
self.animation = animation
self.audio = audio
self.document = document
self.paid_media = paid_media
self.photo = photo
self.sticker = sticker
self.story = story
self.video = video
self.video_note = video_note
self.voice = voice
self.has_media_spoiler = has_media_spoiler
self.contact = contact
self.dice = dice
self.game = game
self.giveaway = giveaway
self.giveaway_winners = giveaway_winners
self.invoice = invoice
self.location = location
self.poll = poll
self.venue = venue
@staticmethod
async def _parse(
client,
reply: "raw.types.MessageReplyHeader",
users: Dict[int, "raw.types.User"],
chats: Dict[int, "raw.types.Chat"],
) -> Optional["ExternalReplyInfo"]:
if not isinstance(reply, raw.types.MessageReplyHeader):
return None
if not reply.reply_from:
return None
animation = None
audio = None
document = None
paid_media = None
photo = None
sticker = None
story = None
video = None
video_note = None
voice = None
contact = None
dice = None
game = None
giveaway = None
giveaway_winners = None
invoice = None
location = None
poll = None
venue = None
media = reply.reply_media
media_type = None
has_media_spoiler = None
if media:
if isinstance(media, raw.types.MessageMediaPhoto):
photo = types.Photo._parse(client, media.photo, media.ttl_seconds)
media_type = enums.MessageMediaType.PHOTO
has_media_spoiler = media.spoiler
elif isinstance(media, raw.types.MessageMediaGeo):
location = types.Location._parse(client, media.geo)
media_type = enums.MessageMediaType.LOCATION
elif isinstance(media, raw.types.MessageMediaContact):
contact = types.Contact._parse(client, media)
media_type = enums.MessageMediaType.CONTACT
elif isinstance(media, raw.types.MessageMediaVenue):
venue = types.Venue._parse(client, media)
media_type = enums.MessageMediaType.VENUE
elif isinstance(media, raw.types.MessageMediaGame):
game = types.Game._parse(client, media)
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_winners = await types.GiveawayWinners._parse(client, media, users, chats)
media_type = enums.MessageMediaType.GIVEAWAY_WINNERS
elif isinstance(media, raw.types.MessageMediaInvoice):
invoice = types.Invoice._parse(client, media)
media_type = enums.MessageMediaType.INVOICE
elif isinstance(media, raw.types.MessageMediaStory):
story = await types.Story._parse(client, media, media.peer, users, chats)
media_type = enums.MessageMediaType.STORY
elif isinstance(media, raw.types.MessageMediaDocument):
doc = media.document
if isinstance(doc, raw.types.Document):
attributes = {type(i): i for i in doc.attributes}
file_name = getattr(
attributes.get(
raw.types.DocumentAttributeFilename, None
), "file_name", None
)
if raw.types.DocumentAttributeAnimated in attributes:
video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None)
animation = types.Animation._parse(client, doc, video_attributes, file_name)
media_type = enums.MessageMediaType.ANIMATION
has_media_spoiler = media.spoiler
elif raw.types.DocumentAttributeSticker in attributes:
sticker = await types.Sticker._parse(client, doc, attributes)
media_type = enums.MessageMediaType.STICKER
elif raw.types.DocumentAttributeVideo in attributes:
video_attributes = attributes[raw.types.DocumentAttributeVideo]
if video_attributes.round_message:
video_note = types.VideoNote._parse(client, doc, video_attributes, media.ttl_seconds)
media_type = enums.MessageMediaType.VIDEO_NOTE
else:
video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds, media.video_cover, media.video_timestamp, media.alt_documents)
media_type = enums.MessageMediaType.VIDEO
has_media_spoiler = media.spoiler
elif raw.types.DocumentAttributeAudio in attributes:
audio_attributes = attributes[raw.types.DocumentAttributeAudio]
if audio_attributes.voice:
voice = types.Voice._parse(client, doc, audio_attributes, media.ttl_seconds)
media_type = enums.MessageMediaType.VOICE
else:
audio = types.Audio._parse(client, doc, audio_attributes, file_name)
media_type = enums.MessageMediaType.AUDIO
else:
document = types.Document._parse(client, doc, file_name)
media_type = enums.MessageMediaType.DOCUMENT
elif isinstance(media, raw.types.MessageMediaPoll):
poll = types.Poll._parse(client, media)
media_type = enums.MessageMediaType.POLL
elif isinstance(media, raw.types.MessageMediaDice):
dice = types.Dice._parse(client, media)
media_type = enums.MessageMediaType.DICE
elif isinstance(media, raw.types.MessageMediaPaidMedia):
paid_media = types.PaidMediaInfo._parse(client, media)
media_type = enums.MessageMediaType.PAID_MEDIA
else:
media = None
return ExternalReplyInfo(
origin=types.MessageOrigin._parse(
client,
reply.reply_from,
users,
chats,
),
chat=types.Chat._parse_chat(
client,
chats.get(utils.get_raw_peer_id(reply.reply_to_peer_id)),
),
message_id=reply.reply_to_msg_id,
link_preview_options=types.LinkPreviewOptions._parse(reply.reply_media),
media=media_type,
animation=animation,
audio=audio,
document=document,
paid_media=paid_media,
photo=photo,
sticker=sticker,
story=story,
video=video,
video_note=video_note,
voice=voice,
has_media_spoiler=has_media_spoiler,
contact=contact,
dice=dice,
game=game,
giveaway=giveaway,
giveaway_winners=giveaway_winners,
invoice=invoice,
location=location,
poll=poll,
venue=venue
)

View file

@ -69,12 +69,11 @@ class Game(Object):
self.animation = animation self.animation = animation
@staticmethod @staticmethod
def _parse(client, message: "raw.types.Message") -> "Game": def _parse(client, media: "raw.types.MessageMediaGame") -> "Game":
game: "raw.types.Game" = message.media.game
animation = None animation = None
if game.document: if media.game.document:
attributes = {type(i): i for i in game.document.attributes} attributes = {type(i): i for i in media.game.document.attributes}
file_name = getattr( file_name = getattr(
attributes.get( attributes.get(
@ -84,17 +83,17 @@ class Game(Object):
animation = types.Animation._parse( animation = types.Animation._parse(
client, client,
game.document, media.game.document,
attributes.get(raw.types.DocumentAttributeVideo, None), attributes.get(raw.types.DocumentAttributeVideo, None),
file_name file_name
) )
return Game( return Game(
id=game.id, id=media.game.id,
title=game.title, title=media.game.title,
short_name=game.short_name, short_name=media.game.short_name,
description=game.description, description=media.game.description,
photo=types.Photo._parse(client, game.photo), photo=types.Photo._parse(client, media.game.photo),
animation=animation, animation=animation,
client=client client=client
) )

View file

@ -73,7 +73,6 @@ class Giveaway(Object):
stars: int = None, stars: int = None,
additional_price: str = None, additional_price: str = None,
allowed_countries: List[str] = None, allowed_countries: List[str] = None,
private_channel_ids: List[int] = None,
is_winners_hidden: bool = None is_winners_hidden: bool = None
): ):
super().__init__(client) super().__init__(client)
@ -86,28 +85,12 @@ class Giveaway(Object):
self.new_subscribers = new_subscribers self.new_subscribers = new_subscribers
self.additional_price = additional_price self.additional_price = additional_price
self.allowed_countries = allowed_countries self.allowed_countries = allowed_countries
self.private_channel_ids = private_channel_ids
self.is_winners_hidden = is_winners_hidden self.is_winners_hidden = is_winners_hidden
@staticmethod @staticmethod
async def _parse(client, message: "raw.types.Message") -> "Giveaway": async def _parse(client, message: "raw.types.Message", chats: dict) -> "Giveaway":
giveaway: "raw.types.MessageMediaGiveaway" = message.media giveaway: "raw.types.MessageMediaGiveaway" = message.media
chats = [] chats = types.List([types.Chat._parse_channel_chat(client, chats.get(i)) for i in giveaway.channels])
private_ids = []
for raw_chat_id in giveaway.channels:
chat_id = utils.get_channel_id(raw_chat_id)
try:
chat = await client.invoke(
raw.functions.channels.GetChannels(
id=[await client.resolve_peer(chat_id)]
)
)
except FloodWait as e:
await asyncio.sleep(e.value)
except Exception:
private_ids.append(chat_id)
else:
chats.append(types.Chat._parse_chat(client, chat.chats[0]))
return Giveaway( return Giveaway(
chats=chats, chats=chats,
@ -118,7 +101,6 @@ class Giveaway(Object):
new_subscribers=giveaway.only_new_subscribers, new_subscribers=giveaway.only_new_subscribers,
additional_price=giveaway.prize_description, additional_price=giveaway.prize_description,
allowed_countries=giveaway.countries_iso2 if len(giveaway.countries_iso2) > 0 else None, allowed_countries=giveaway.countries_iso2 if len(giveaway.countries_iso2) > 0 else None,
private_channel_ids=private_ids if len(private_ids) > 0 else None,
is_winners_hidden=not giveaway.winners_are_visible, is_winners_hidden=not giveaway.winners_are_visible,
client=client client=client
) )

View file

@ -0,0 +1,87 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Optional
from pyrogram import raw
from ..object import Object
class LinkPreviewOptions(Object):
"""Describes the options used for link preview generation.
Parameters:
is_disabled (``bool``, *optional*):
True, if the link preview is disabled.
url (``str``, *optional*):
URL to use for the link preview.
If empty, then the first URL found in the message text will be used.
prefer_small_media (``bool``, *optional*):
True, if the media in the link preview is suppposed to be shrunk.
Ignored if the URL isn't explicitly specified or media size change isn't supported for the preview.
prefer_large_media (``bool``, *optional*):
True, if the media in the link preview is suppposed to be enlarged.
Ignored if the URL isn't explicitly specified or media size change isn't supported for the preview.
invert_media (``bool``, *optional*):
True, if the link preview must be shown above the message text.
Otherwise, the link preview will be shown below the message text.
"""
def __init__(
self,
*,
is_disabled: bool = None,
url: str = None,
prefer_small_media: bool = None,
prefer_large_media: bool = None,
invert_media: bool = None
):
super().__init__()
self.is_disabled = is_disabled
self.url = url
self.prefer_small_media = prefer_small_media
self.prefer_large_media = prefer_large_media
self.invert_media = invert_media
@staticmethod
def _parse(
media: "raw.types.MessageMediaWebPage",
url: str = None,
invert_media: bool = None
) -> Optional["LinkPreviewOptions"]:
if isinstance(media, raw.types.MessageMediaWebPage) and not isinstance(media.webpage, raw.types.WebPageNotModified):
return LinkPreviewOptions(
is_disabled=False,
url=media.webpage.url,
prefer_small_media=media.force_small_media,
prefer_large_media=media.force_large_media,
invert_media=invert_media,
)
if url:
return LinkPreviewOptions(
is_disabled=True,
url=url,
invert_media=invert_media,
)

View file

@ -88,23 +88,8 @@ class Message(Object, Update):
Topic the message belongs to. Topic the message belongs to.
only returned using when client.get_messages. only returned using when client.get_messages.
forward_from (:obj:`~pyrogram.types.User`, *optional*): forward_origin (:obj:`~pyrogram.types.MessageOrigin`, *optional*):
For forwarded messages, sender of the original message. Information about the original message for forwarded messages.
forward_sender_name (``str``, *optional*):
For messages forwarded from users who have hidden their accounts, name of the user.
forward_from_chat (:obj:`~pyrogram.types.Chat`, *optional*):
For messages forwarded from channels, information about the original channel. For messages forwarded from anonymous group administrators, information about the original supergroup.
forward_from_message_id (``int``, *optional*):
For messages forwarded from channels, identifier of the original message in the channel.
forward_signature (``str``, *optional*):
For messages forwarded from channels, signature of the post author if present.
forward_date (:py:obj:`~datetime.datetime`, *optional*):
For forwarded messages, date the original message was sent.
is_topic_message (``bool``, *optional*): is_topic_message (``bool``, *optional*):
True, if the message is sent to a forum topic True, if the message is sent to a forum topic
@ -187,11 +172,8 @@ class Message(Object, Update):
For messages with a caption, special entities like usernames, URLs, bot commands, etc. that appear For messages with a caption, special entities like usernames, URLs, bot commands, etc. that appear
in the caption. in the caption.
quote_text (``str``, *optional*): quote (:obj:`~pyrogram.types.TextQuote`, *optional*):
Quoted reply text. Chosen quote from the replied message.
quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
For quote text, special entities like usernames, URLs, bot commands, etc. that appear in the quote text.
effect_id (``str``, *optional*): effect_id (``str``, *optional*):
Unique identifier of the message effect added to the message. Unique identifier of the message effect added to the message.
@ -260,6 +242,9 @@ class Message(Object, Update):
venue (:obj:`~pyrogram.types.Venue`, *optional*): venue (:obj:`~pyrogram.types.Venue`, *optional*):
Message is a venue, information about the venue. Message is a venue, information about the venue.
link_preview_options (:obj:`~pyrogram.types.LinkPreviewOptions`, *optional*):
Options used for link preview generation for the message.
poll (:obj:`~pyrogram.types.Poll`, *optional*): poll (:obj:`~pyrogram.types.Poll`, *optional*):
Message is a native poll, information about the poll. Message is a native poll, information about the poll.
@ -336,6 +321,9 @@ class Message(Object, Update):
Messages sent from yourself to other chats are outgoing (*outgoing* is True). Messages sent from yourself to other chats are outgoing (*outgoing* is True).
An exception is made for your own personal chat; messages sent there will be incoming. An exception is made for your own personal chat; messages sent there will be incoming.
external_reply (:obj:`~pyrogram.types.ExternalReplyInfo`, *optional*):
Information about the message that is being replied to, which may come from another chat or forum topic.
matches (List of regex Matches, *optional*): matches (List of regex Matches, *optional*):
A list containing all `Match Objects <https://docs.python.org/3/library/re.html#match-objects>`_ that match A list containing all `Match Objects <https://docs.python.org/3/library/re.html#match-objects>`_ that match
the text of this message. Only applicable when using :obj:`Filters.regex <pyrogram.Filters.regex>`. the text of this message. Only applicable when using :obj:`Filters.regex <pyrogram.Filters.regex>`.
@ -451,12 +439,7 @@ class Message(Object, Update):
date: datetime = None, date: datetime = None,
chat: "types.Chat" = None, chat: "types.Chat" = None,
topic: "types.ForumTopic" = None, topic: "types.ForumTopic" = None,
forward_from: "types.User" = None, forward_origin: "types.MessageOrigin" = 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, is_topic_message: bool = None,
reply_to_chat_id: int = None, reply_to_chat_id: int = None,
reply_to_message_id: int = None, reply_to_message_id: int = None,
@ -481,8 +464,7 @@ class Message(Object, Update):
text: Str = None, text: Str = None,
entities: List["types.MessageEntity"] = None, entities: List["types.MessageEntity"] = None,
caption_entities: List["types.MessageEntity"] = None, caption_entities: List["types.MessageEntity"] = None,
quote_text: str = None, quote: "types.TextQuote" = None,
quote_entities: List["types.MessageEntity"] = None,
effect_id: str = None, effect_id: str = None,
invert_media: bool = None, invert_media: bool = None,
audio: "types.Audio" = None, audio: "types.Audio" = None,
@ -512,6 +494,7 @@ class Message(Object, Update):
contact: "types.Contact" = None, contact: "types.Contact" = None,
location: "types.Location" = None, location: "types.Location" = None,
venue: "types.Venue" = None, venue: "types.Venue" = None,
link_preview_options: "types.LinkPreviewOptions" = None,
poll: "types.Poll" = None, poll: "types.Poll" = None,
dice: "types.Dice" = None, dice: "types.Dice" = None,
new_chat_members: List["types.User"] = None, new_chat_members: List["types.User"] = None,
@ -531,6 +514,7 @@ class Message(Object, Update):
forwards: int = None, forwards: int = None,
via_bot: "types.User" = None, via_bot: "types.User" = None,
outgoing: bool = None, outgoing: bool = None,
external_reply: Optional["types.ExternalReplyInfo"] = None,
matches: List[Match] = None, matches: List[Match] = None,
command: List[str] = None, command: List[str] = None,
bot_allowed: "types.BotAllowed" = None, bot_allowed: "types.BotAllowed" = None,
@ -570,13 +554,8 @@ class Message(Object, Update):
self.sender_business_bot = sender_business_bot self.sender_business_bot = sender_business_bot
self.date = date self.date = date
self.chat = chat self.chat = chat
self.topic = topic self.forward_origin = forward_origin
self.forward_from = forward_from self.external_reply = external_reply
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.is_topic_message = is_topic_message
self.reply_to_chat_id = reply_to_chat_id self.reply_to_chat_id = reply_to_chat_id
self.reply_to_message_id = reply_to_message_id self.reply_to_message_id = reply_to_message_id
@ -601,8 +580,7 @@ class Message(Object, Update):
self.text = text self.text = text
self.entities = entities self.entities = entities
self.caption_entities = caption_entities self.caption_entities = caption_entities
self.quote_text = quote_text self.quote = quote
self.quote_entities = quote_entities
self.effect_id = effect_id self.effect_id = effect_id
self.invert_media = invert_media self.invert_media = invert_media
self.audio = audio self.audio = audio
@ -633,6 +611,7 @@ class Message(Object, Update):
self.contact = contact self.contact = contact
self.location = location self.location = location
self.venue = venue self.venue = venue
self.link_preview_options = link_preview_options
self.poll = poll self.poll = poll
self.dice = dice self.dice = dice
self.new_chat_members = new_chat_members self.new_chat_members = new_chat_members
@ -1008,31 +987,18 @@ class Message(Object, Update):
entities = types.List(filter(lambda x: x is not None, entities)) entities = types.List(filter(lambda x: x is not None, entities))
sender_business_bot = None sender_business_bot = None
forward_from = None
forward_sender_name = None
forward_from_chat = None
forward_from_message_id = None
forward_signature = None
forward_date = None
is_topic_message = None is_topic_message = None
forward_header = message.fwd_from # type: raw.types.MessageFwdHeader forward_header = message.fwd_from # type: raw.types.MessageFwdHeader
forward_origin = None
if forward_header: if forward_header:
forward_date = utils.timestamp_to_datetime(forward_header.date) forward_origin = types.MessageOrigin._parse(
client,
if forward_header.from_id: forward_header,
raw_peer_id = utils.get_raw_peer_id(forward_header.from_id) users,
peer_id = utils.get_peer_id(forward_header.from_id) chats,
)
if peer_id > 0:
forward_from = types.User._parse(client, users[raw_peer_id])
else:
forward_from_chat = types.Chat._parse_channel_chat(client, chats[raw_peer_id])
forward_from_message_id = forward_header.channel_post
forward_signature = forward_header.post_author
elif forward_header.from_name:
forward_sender_name = forward_header.from_name
photo = None photo = None
paid_media = None paid_media = None
@ -1053,6 +1019,7 @@ class Message(Object, Update):
web_page_preview = None web_page_preview = None
sticker = None sticker = None
document = None document = None
link_preview_options = None
poll = None poll = None
dice = None dice = None
@ -1075,10 +1042,10 @@ class Message(Object, Update):
venue = types.Venue._parse(client, media) venue = types.Venue._parse(client, media)
media_type = enums.MessageMediaType.VENUE media_type = enums.MessageMediaType.VENUE
elif isinstance(media, raw.types.MessageMediaGame): elif isinstance(media, raw.types.MessageMediaGame):
game = types.Game._parse(client, message) game = types.Game._parse(client, media)
media_type = enums.MessageMediaType.GAME media_type = enums.MessageMediaType.GAME
elif isinstance(media, raw.types.MessageMediaGiveaway): elif isinstance(media, raw.types.MessageMediaGiveaway):
giveaway = await types.Giveaway._parse(client, message) giveaway = await types.Giveaway._parse(client, message, chats)
media_type = enums.MessageMediaType.GIVEAWAY media_type = enums.MessageMediaType.GIVEAWAY
elif isinstance(media, raw.types.MessageMediaGiveawayResults): elif isinstance(media, raw.types.MessageMediaGiveawayResults):
giveaway_result = await types.GiveawayResult._parse(client, message.media) giveaway_result = await types.GiveawayResult._parse(client, message.media)
@ -1165,6 +1132,12 @@ class Message(Object, Update):
else: else:
media = None media = None
link_preview_options = types.LinkPreviewOptions._parse(
media,
getattr(getattr(media, "webpage", None), "url", utils.get_first_url(message.message)),
message.invert_media
)
reply_markup = message.reply_markup reply_markup = message.reply_markup
if reply_markup: if reply_markup:
@ -1219,12 +1192,7 @@ class Message(Object, Update):
author_signature=message.post_author, author_signature=message.post_author,
has_protected_content=message.noforwards, has_protected_content=message.noforwards,
has_media_spoiler=has_media_spoiler, has_media_spoiler=has_media_spoiler,
forward_from=forward_from, forward_origin=forward_origin,
forward_sender_name=forward_sender_name,
forward_from_chat=forward_from_chat,
forward_from_message_id=forward_from_message_id,
forward_signature=forward_signature,
forward_date=forward_date,
is_topic_message=is_topic_message, is_topic_message=is_topic_message,
mentioned=message.mentioned, mentioned=message.mentioned,
scheduled=is_scheduled, scheduled=is_scheduled,
@ -1253,6 +1221,7 @@ class Message(Object, Update):
web_page_preview=web_page_preview, web_page_preview=web_page_preview,
sticker=sticker, sticker=sticker,
document=document, document=document,
link_preview_options=link_preview_options,
poll=poll, poll=poll,
dice=dice, dice=dice,
views=message.views, views=message.views,
@ -1269,11 +1238,19 @@ class Message(Object, Update):
parsed_message.sender_chat = sender_chat parsed_message.sender_chat = sender_chat
if message.reply_to: if message.reply_to:
parsed_message.external_reply = await types.ExternalReplyInfo._parse(
client,
message.reply_to,
users,
chats
)
if isinstance(message.reply_to, raw.types.MessageReplyHeader): if isinstance(message.reply_to, raw.types.MessageReplyHeader):
parsed_message.quote_text = message.reply_to.quote_text if message.reply_to.quote:
if len(message.reply_to.quote_entities) > 0: parsed_message.quote = types.TextQuote._parse(
quote_entities = [types.MessageEntity._parse(client, entity, users) for entity in message.reply_to.quote_entities] client,
parsed_message.quote_entities = types.List(filter(lambda x: x is not None, quote_entities)) users,
message.reply_to
)
if message.reply_to.forum_topic: if message.reply_to.forum_topic:
if message.reply_to.reply_to_top_id: if message.reply_to.reply_to_top_id:
thread_id = message.reply_to.reply_to_top_id thread_id = message.reply_to.reply_to_top_id
@ -1309,20 +1286,15 @@ class Message(Object, Update):
if replies: if replies:
if parsed_message.reply_to_message_id: if parsed_message.reply_to_message_id:
if rtci is not None and parsed_message.chat.id != reply_to_chat_id:
key = (reply_to_chat_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: try:
key = (parsed_message.chat.id, parsed_message.reply_to_message_id)
reply_to_message = client.message_cache[key] reply_to_message = client.message_cache[key]
if not reply_to_message: if not reply_to_message:
reply_to_message = await client.get_messages( reply_to_message = await client.get_messages(
replies=replies - 1, parsed_message.chat.id,
**reply_to_params reply_to_message_ids=message.id,
replies=replies - 1
) )
if reply_to_message and not reply_to_message.forum_topic_created: if reply_to_message and not reply_to_message.forum_topic_created:
parsed_message.reply_to_message = reply_to_message parsed_message.reply_to_message = reply_to_message
@ -1363,6 +1335,60 @@ class Message(Object, Update):
def content(self) -> str: def content(self) -> str:
return self.text or self.caption or Str("").init([]) return self.text or self.caption or Str("").init([])
# region Deprecated
# TODO: Remove later
@property
def forward_from(self) -> Optional["types.User"]:
log.warning(
"`message.forward_from` is deprecated and will be removed in future updates. Use `message.forward_origin.sender_user` instead."
)
return getattr(self.forward_origin, "sender_user", None)
@property
def forward_sender_name(self) -> Optional[str]:
log.warning(
"`message.forward_sender_name` property is deprecated and will be removed in future updates. Use `message.forward_origin.sender_user_name` instead."
)
return getattr(self.forward_origin, "sender_user_name", None)
@property
def forward_from_chat(self) -> Optional["types.Chat"]:
log.warning(
"`message.forward_from_chat` property is deprecated and will be removed in future updates. Use `message.forward_origin.chat.sender_chat` instead."
)
return getattr(
self.forward_origin,
"chat",
getattr(
self.forward_origin,
"sender_chat",
None
)
)
@property
def forward_from_message_id(self) -> Optional[int]:
log.warning(
"`message.forward_from_message_id` property is deprecated and will be removed in future updates. Use `message.forward_origin.message_id` instead."
)
return getattr(self.forward_origin, "message_id", None)
@property
def forward_signature(self) -> Optional[str]:
log.warning(
"`message.forward_signature` property is deprecated and will be removed in future updates. Use `message.forward_origin.author_signature` instead."
)
return getattr(self.forward_origin, "author_signature", None)
@property
def forward_date(self) -> Optional[datetime]:
log.warning(
"`message.forward_date` property is deprecated and will be removed in future updates. Use `message.forward_origin.date` instead."
)
return getattr(self.forward_origin, "date", None)
# endregion
async def get_media_group(self) -> List["types.Message"]: async def get_media_group(self) -> List["types.Message"]:
"""Bound method *get_media_group* of :obj:`~pyrogram.types.Message`. """Bound method *get_media_group* of :obj:`~pyrogram.types.Message`.
@ -1398,7 +1424,7 @@ class Message(Object, Update):
quote: bool = None, quote: bool = None,
parse_mode: Optional["enums.ParseMode"] = None, parse_mode: Optional["enums.ParseMode"] = None,
entities: List["types.MessageEntity"] = None, entities: List["types.MessageEntity"] = None,
disable_web_page_preview: bool = None, link_preview_options: "types.LinkPreviewOptions" = None,
disable_notification: bool = None, disable_notification: bool = None,
reply_to_message_id: int = None, reply_to_message_id: int = None,
business_connection_id: str = None, business_connection_id: str = None,
@ -1410,7 +1436,8 @@ class Message(Object, Update):
allow_paid_broadcast: bool = None, allow_paid_broadcast: bool = None,
message_effect_id: int = None, message_effect_id: int = None,
invert_media: bool = None, invert_media: bool = None,
reply_markup=None reply_markup=None,
disable_web_page_preview: bool = None
) -> "Message": ) -> "Message":
"""Bound method *reply_text* of :obj:`~pyrogram.types.Message`. """Bound method *reply_text* of :obj:`~pyrogram.types.Message`.
@ -1450,9 +1477,8 @@ class Message(Object, Update):
disable_web_page_preview (``bool``, *optional*): disable_web_page_preview (``bool``, *optional*):
Disables link previews for links in this message. Disables link previews for links in this message.
disable_notification (``bool``, *optional*): link_preview_options (:obj:`~pyrogram.types.LinkPreviewOptions`, *optional*):
Sends the message silently. Options used for link preview generation for the message.
Users will receive a notification with no sound.
reply_to_message_id (``int``, *optional*): reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message. If the message is a reply, ID of the original message.
@ -1525,6 +1551,7 @@ class Message(Object, Update):
parse_mode=parse_mode, parse_mode=parse_mode,
entities=entities, entities=entities,
disable_web_page_preview=disable_web_page_preview, disable_web_page_preview=disable_web_page_preview,
link_preview_options=link_preview_options,
disable_notification=disable_notification, disable_notification=disable_notification,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
@ -4312,10 +4339,11 @@ class Message(Object, Update):
text: str, text: str,
parse_mode: Optional["enums.ParseMode"] = None, parse_mode: Optional["enums.ParseMode"] = None,
entities: List["types.MessageEntity"] = None, entities: List["types.MessageEntity"] = None,
disable_web_page_preview: bool = None, link_preview_options: "types.LinkPreviewOptions" = None,
invert_media: bool = None, invert_media: bool = None,
reply_markup: "types.InlineKeyboardMarkup" = None, reply_markup: "types.InlineKeyboardMarkup" = None,
business_connection_id: str = None business_connection_id: str = None,
disable_web_page_preview: bool = None
) -> "Message": ) -> "Message":
"""Bound method *edit_text* of :obj:`~pyrogram.types.Message`. """Bound method *edit_text* of :obj:`~pyrogram.types.Message`.
@ -4347,8 +4375,8 @@ class Message(Object, Update):
entities (List of :obj:`~pyrogram.types.MessageEntity`): entities (List of :obj:`~pyrogram.types.MessageEntity`):
List of special entities that appear in message text, which can be specified instead of *parse_mode*. List of special entities that appear in message text, which can be specified instead of *parse_mode*.
disable_web_page_preview (``bool``, *optional*): link_preview_options (:obj:`~pyrogram.types.LinkPreviewOptions`, *optional*):
Disables link previews for links in this message. Options used for link preview generation for the message.
invert_media (``bool``, *optional*): invert_media (``bool``, *optional*):
Inverts the position of the media and caption. Inverts the position of the media and caption.
@ -4373,6 +4401,7 @@ class Message(Object, Update):
parse_mode=parse_mode, parse_mode=parse_mode,
entities=entities, entities=entities,
disable_web_page_preview=disable_web_page_preview, disable_web_page_preview=disable_web_page_preview,
link_preview_options=link_preview_options,
invert_media=invert_media, invert_media=invert_media,
reply_markup=reply_markup, reply_markup=reply_markup,
business_connection_id=self.business_connection_id if business_connection_id is None else business_connection_id business_connection_id=self.business_connection_id if business_connection_id is None else business_connection_id
@ -4747,7 +4776,7 @@ class Message(Object, Update):
text=self.text, text=self.text,
entities=self.entities, entities=self.entities,
parse_mode=enums.ParseMode.DISABLED, parse_mode=enums.ParseMode.DISABLED,
disable_web_page_preview=not self.web_page_preview, link_preview_options=types.LinkPreviewOptions(is_disabled=not self.web_page),
disable_notification=disable_notification, disable_notification=disable_notification,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,

View file

@ -0,0 +1,94 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present <https://github.com/TelegramPlayGround>
#
# 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 <http://www.gnu.org/licenses/>.
from datetime import datetime
from typing import Dict, Optional
import pyrogram
from pyrogram import enums, raw, types, utils
from ..object import Object
class MessageOrigin(Object):
"""This object describes the origin of a message.
It can be one of:
- :obj:`~pyrogram.types.MessageOriginChannel`
- :obj:`~pyrogram.types.MessageOriginChat`
- :obj:`~pyrogram.types.MessageOriginHiddenUser`
- :obj:`~pyrogram.types.MessageOriginImport`
- :obj:`~pyrogram.types.MessageOriginUser`
"""
def __init__(
self,
type: "enums.MessageOriginType",
date: Optional[datetime] = None
):
super().__init__()
self.type = type
self.date = date
@staticmethod
def _parse(
client: "pyrogram.Client",
fwd_from: "raw.types.MessageFwdHeader",
users: Dict[int, "raw.base.User"],
chats: Dict[int, "raw.base.Chat"]
) -> Optional["MessageOrigin"]:
if not fwd_from:
return None
forward_date = utils.timestamp_to_datetime(fwd_from.date)
if fwd_from.from_id:
raw_peer_id = utils.get_raw_peer_id(fwd_from.from_id)
peer_id = utils.get_peer_id(fwd_from.from_id)
peer_type = utils.get_peer_type(peer_id)
if peer_type == "user":
return types.MessageOriginUser(
date=forward_date,
sender_user=types.User._parse(client, users.get(raw_peer_id))
)
else:
if fwd_from.channel_post:
return types.MessageOriginChannel(
date=forward_date,
chat=types.Chat._parse_channel_chat(client, chats.get(raw_peer_id)),
message_id=fwd_from.channel_post,
author_signature=fwd_from.post_author
)
else:
return types.MessageOriginChat(
date=forward_date,
sender_chat=types.Chat._parse_channel_chat(client, chats.get(raw_peer_id)),
author_signature=fwd_from.post_author
)
elif fwd_from.from_name:
return types.MessageOriginHiddenUser(
date=forward_date,
sender_user_name=fwd_from.from_name
)
elif fwd_from.imported:
return types.MessageOriginImport(
date=forward_date,
sender_user_name=fwd_from.post_author
)

View file

@ -0,0 +1,61 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present <https://github.com/TelegramPlayGround>
#
# 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 <http://www.gnu.org/licenses/>.
from datetime import datetime
from pyrogram import enums, types
from .message_origin import MessageOrigin
class MessageOriginChannel(MessageOrigin):
"""The message was originally sent to a channel chat.
Parameters:
type (:obj:`~pyrogram.enums.MessageOriginType`):
Type of the message origin.
date (:py:obj:`~datetime.datetime`):
Date the message was sent originally.
chat (:obj:`~pyrogram.types.Chat`):
Channel chat to which the message was originally sent.
message_id (``int``):
Unique message identifier inside the chat.
author_signature (``str``, *optional*):
Signature of the original post author.
"""
def __init__(
self,
*,
type: "enums.MessageOriginType" = enums.MessageOriginType.CHANNEL,
date: datetime = None,
chat: "types.Chat" = None,
message_id: int = None,
author_signature: str = None
):
super().__init__(
type=type,
date=date
)
self.chat = chat
self.message_id = message_id
self.author_signature = author_signature

View file

@ -0,0 +1,56 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present <https://github.com/TelegramPlayGround>
#
# 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 <http://www.gnu.org/licenses/>.
from datetime import datetime
from pyrogram import enums, types
from .message_origin import MessageOrigin
class MessageOriginChat(MessageOrigin):
"""The message was originally sent on behalf of a chat to a group chat.
Parameters:
type (:obj:`~pyrogram.enums.MessageOriginType`):
Type of the message origin.
date (:py:obj:`~datetime.datetime`):
Date the message was sent originally.
sender_chat (:obj:`~pyrogram.types.Chat`):
Chat that sent the message originally.
author_signature (``str``, *optional*):
For messages originally sent by an anonymous chat administrator, original message author signature.
"""
def __init__(
self,
*,
type: "enums.MessageOriginType" = enums.MessageOriginType.CHAT,
date: datetime = None,
sender_chat: "types.Chat" = None,
author_signature: str = None
):
super().__init__(
type=type,
date=date
)
self.sender_chat = sender_chat
self.author_signature = author_signature

View file

@ -0,0 +1,51 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present <https://github.com/TelegramPlayGround>
#
# 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 <http://www.gnu.org/licenses/>.
from datetime import datetime
from pyrogram import enums
from .message_origin import MessageOrigin
class MessageOriginHiddenUser(MessageOrigin):
"""The message was originally sent by an unknown user.
Parameters:
type (:obj:`~pyrogram.enums.MessageOriginType`):
Type of the message origin.
date (:py:obj:`~datetime.datetime`):
Date the message was sent originally.
sender_user_name (``str``):
Name of the user that sent the message originally.
"""
def __init__(
self,
*,
type: "enums.MessageOriginType" = enums.MessageOriginType.HIDDEN_USER,
date: datetime = None,
sender_user_name: str = None
):
super().__init__(
type=type,
date=date
)
self.sender_user_name = sender_user_name

View file

@ -0,0 +1,50 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present <https://github.com/TelegramPlayGround>
#
# 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 <http://www.gnu.org/licenses/>.
from datetime import datetime
from pyrogram import enums
from .message_origin import MessageOrigin
class MessageOriginImport(MessageOrigin):
"""Contains information about a message imported from a foreign chat service.
Parameters:
type (:obj:`~pyrogram.enums.MessageOriginType`):
Type of the message origin.
date (:py:obj:`~datetime.datetime`):
Date the message was sent originally.
sender_user_name (``str``):
Name of the original sender.
"""
def __init__(
self,
*,
date: datetime = None,
sender_user_name: str = None
):
super().__init__(
type=enums.MessageOriginType.IMPORT,
date=date
)
self.sender_user_name = sender_user_name

View file

@ -0,0 +1,51 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present <https://github.com/TelegramPlayGround>
#
# 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 <http://www.gnu.org/licenses/>.
from datetime import datetime
from pyrogram import enums, types
from .message_origin import MessageOrigin
class MessageOriginUser(MessageOrigin):
"""The message was originally sent by a known user.
Parameters:
type (:obj:`~pyrogram.enums.MessageOriginType`):
Type of the message origin.
date (:py:obj:`~datetime.datetime`):
Date the message was sent originally.
sender_user (:obj:`~pyrogram.types.User`):
User that sent the message originally.
"""
def __init__(
self,
*,
type: "enums.MessageOriginType" = enums.MessageOriginType.USER,
date: datetime = None,
sender_user: "types.User" = None
):
super().__init__(
type=type,
date=date
)
self.sender_user = sender_user

View file

@ -327,12 +327,13 @@ class Story(Object, Update):
text: str, text: str,
parse_mode: Optional["enums.ParseMode"] = None, parse_mode: Optional["enums.ParseMode"] = None,
entities: List["types.MessageEntity"] = None, entities: List["types.MessageEntity"] = None,
disable_web_page_preview: bool = None, link_preview_options: "types.LinkPreviewOptions" = None,
disable_notification: bool = None, disable_notification: bool = None,
reply_to_story_id: int = None, reply_to_story_id: int = None,
schedule_date: datetime = None, schedule_date: datetime = None,
protect_content: bool = None, protect_content: bool = None,
reply_markup=None reply_markup=None,
disable_web_page_preview: bool = None
) -> "types.Message": ) -> "types.Message":
"""Bound method *reply_text* of :obj:`~pyrogram.types.Story`. """Bound method *reply_text* of :obj:`~pyrogram.types.Story`.
@ -364,8 +365,8 @@ class Story(Object, Update):
entities (List of :obj:`~pyrogram.types.MessageEntity`): entities (List of :obj:`~pyrogram.types.MessageEntity`):
List of special entities that appear in message text, which can be specified instead of *parse_mode*. List of special entities that appear in message text, which can be specified instead of *parse_mode*.
disable_web_page_preview (``bool``, *optional*): link_preview_options (:obj:`~pyrogram.types.LinkPreviewOptions`, *optional*):
Disables link previews for links in this message. Options used for link preview generation for the message.
disable_notification (``bool``, *optional*): disable_notification (``bool``, *optional*):
Sends the message silently. Sends the message silently.
@ -400,6 +401,7 @@ class Story(Object, Update):
parse_mode=parse_mode, parse_mode=parse_mode,
entities=entities, entities=entities,
disable_web_page_preview=disable_web_page_preview, disable_web_page_preview=disable_web_page_preview,
link_preview_options=link_preview_options,
disable_notification=disable_notification, disable_notification=disable_notification,
reply_to_story_id=reply_to_story_id, reply_to_story_id=reply_to_story_id,
schedule_date=schedule_date, schedule_date=schedule_date,

View file

@ -0,0 +1,83 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Dict, List, Optional
import pyrogram
from pyrogram import raw, types
from ..messages_and_media.message import Str
from ..object import Object
class TextQuote(Object):
"""Describes manually or automatically chosen quote from another message.
Parameters:
text (``str``):
Text of the quoted part of a message that is replied to by the given message.
entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
Special entities that appear in the quote.
Currently, only bold, italic, underline, strikethrough, spoiler, and custom_emoji entities are kept in quotes.
position (``int``):
Approximate quote position in the original message in UTF-16 code units as specified by the sender.
is_manual (``bool``, *optional*):
True, if the quote was chosen manually by the message sender.
Otherwise, the quote was added automatically by the server.
"""
def __init__(
self, *,
text: Optional[str] = None,
entities: Optional[List["types.MessageEntity"]] = None,
position: Optional[int] = None,
is_manual: Optional[bool] = None
):
super().__init__()
self.text = text
self.entities = entities
self.position = position
self.is_manual = is_manual
@staticmethod
def _parse(
client: "pyrogram.Client",
users: Dict[int, "raw.types.User"],
reply_to: "raw.types.MessageReplyHeader"
) -> "TextQuote":
if isinstance(reply_to, raw.types.MessageReplyHeader):
entities = types.List(
filter(
lambda x: x is not None,
[
types.MessageEntity._parse(client, entity, users)
for entity in getattr(reply_to, "quote_entities", [])
]
)
)
return TextQuote(
text=Str(reply_to.quote_text).init(entities) or None,
entities=entities or None,
position=reply_to.quote_offset or 0,
is_manual=reply_to.quote
)

View file

@ -22,6 +22,7 @@ from pyrogram import raw
from pyrogram import types from pyrogram import types
from ..object import Object from ..object import Object
from typing import Optional
class WebPage(Object): class WebPage(Object):
# TODO: hash, cached_page # TODO: hash, cached_page

View file

@ -21,6 +21,7 @@ import asyncio
import base64 import base64
import functools import functools
import hashlib import hashlib
import re
import os import os
import struct import struct
from concurrent.futures.thread import ThreadPoolExecutor from concurrent.futures.thread import ThreadPoolExecutor
@ -120,9 +121,14 @@ async def parse_messages(
if replies: if replies:
messages_with_replies = { messages_with_replies = {
i.id: i.reply_to i.id: i.reply_to.reply_to_msg_id
for i in messages.messages for i in messages.messages
if not isinstance(i, raw.types.MessageEmpty) and i.reply_to and isinstance(i.reply_to, raw.types.MessageReplyHeader) if (
not isinstance(i, raw.types.MessageEmpty)
and i.reply_to
and isinstance(i.reply_to, raw.types.MessageReplyHeader)
and i.reply_to.reply_to_msg_id is not None
)
} }
message_reply_to_story = { message_reply_to_story = {
@ -135,61 +141,27 @@ async def parse_messages(
# We need a chat id, but some messages might be empty (no chat attribute available) # 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) # Scan until we find a message with a chat available (there must be one, because we are fetching replies)
for m in parsed_messages: for m in parsed_messages:
if not isinstance(m, types.Message):
continue
if m.chat: if m.chat:
chat_id = m.chat.id chat_id = m.chat.id
break break
else: else:
chat_id = 0 chat_id = 0
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
reply_messages = await client.get_messages( reply_messages = await client.get_messages(
chat_id, chat_id,
reply_to_message_ids=messages_with_replies.keys(), reply_to_message_ids=messages_with_replies.keys(),
replies=replies - 1 replies=replies - 1
) )
else:
# slow path: fetch all messages individually
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 = 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,
replies=replies - 1
)
if isinstance(to_be_added_msg, list):
for current_to_be_added in to_be_added_msg:
reply_messages.append(current_to_be_added)
elif to_be_added_msg:
reply_messages.append(to_be_added_msg)
for message in parsed_messages: for message in parsed_messages:
reply_to = messages_with_replies.get(message.id, None) reply_id = messages_with_replies.get(message.id, None)
if not reply_to:
continue
reply_id = reply_to.reply_to_msg_id
for reply in reply_messages: for reply in reply_messages:
if reply.id == reply_id and not reply.forum_topic_created: if reply.id == reply_id:
if not reply.forum_topic_created:
message.reply_to_message = reply message.reply_to_message = reply
if message_reply_to_story: if message_reply_to_story:
for m in parsed_messages: for m in parsed_messages:
if not isinstance(m, types.Message):
continue
if m.chat: if m.chat:
chat_id = m.chat.id chat_id = m.chat.id
break break
@ -197,7 +169,7 @@ async def parse_messages(
chat_id = 0 chat_id = 0
reply_messages = {} reply_messages = {}
for msg_id in message_reply_to_story: for msg_id in message_reply_to_story.keys():
reply_messages[msg_id] = await client.get_stories( reply_messages[msg_id] = await client.get_stories(
message_reply_to_story[msg_id]['user_id'], message_reply_to_story[msg_id]['user_id'],
message_reply_to_story[msg_id]['story_id'] message_reply_to_story[msg_id]['story_id']
@ -529,3 +501,11 @@ async def get_reply_to(
story_id=reply_to_story_id story_id=reply_to_story_id
) )
return reply_to return reply_to
def get_first_url(text):
text = re.sub(r"^\s*(<[\w<>=\s\"]*>)\s*", r"\1", text)
text = re.sub(r"\s*(</[\w</>]*>)\s*$", r"\1", text)
matches = re.findall(r"(https?):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])", text)
return f"{matches[0][0]}://{matches[0][1]}{matches[0][2]}" if matches else None