Compare commits

...

6 commits

Author SHA1 Message Date
wulan17
fb7d5a46a2
pyrofork: Fix critical issue
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-26 22:36:09 +07:00
wulan17
8b2ae07ea0
pyrofork: Refactor ShippingQuery
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-26 22:35:53 +07:00
wulan17
62dd3c51ba
pyrofork: Refactor Giveaway
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-26 22:35:53 +07:00
wulan17
b414831b72
pyrofork: utils: Don't append message to messages_with_replies if reply_to_msg_id is None
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-26 22:35:53 +07:00
wulan17
a5698e73a9
Revert "Pyrofork: types: message: Fix cross chat reply parsing"
This reverts commit b4cb8ff17c.

Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-26 22:35:52 +07:00
KurimuzonAkuma
98ae2bee64
Add ExternalReplyInfo
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-03-26 22:35:52 +07:00
29 changed files with 510 additions and 156 deletions

View file

@ -442,12 +442,12 @@ def start(format: bool = False):
references, count = get_references(c.qualname, "constructors")
if references:
docstring += f"\n Functions:\n This object can be returned by " \
docstring += "\n Functions:\n This object can be returned by " \
f"{count} function{'s' if count > 1 else ''}.\n\n" \
f" .. currentmodule:: pyrogram.raw.functions\n\n" \
f" .. autosummary::\n" \
f" :nosignatures:\n\n" \
f" " + references
" .. currentmodule:: pyrogram.raw.functions\n\n" \
" .. autosummary::\n" \
" :nosignatures:\n\n" \
" " + references
write_types = read_types = "" if c.has_flags else "# No flags\n "

View file

@ -524,6 +524,7 @@ def pyrogram_api():
Audio
AvailableEffect
Document
ExternalReplyInfo
AlternativeVideo
Animation
Video

View file

@ -29,7 +29,7 @@ from pygments.styles.friendly import FriendlyStyle
FriendlyStyle.background_color = "#f3f2f1"
project = "Pyrofork"
copyright = f"2022-present, Mayuri-Chan"
copyright = "2022-present, Mayuri-Chan"
author = "Mayuri-Chan"
version = ".".join(__version__.split(".")[:-1])

View file

@ -17,13 +17,17 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from concurrent.futures.thread import ThreadPoolExecutor
from . import raw, types, filters, handlers, emoji, enums
from .client import Client
from .sync import idle, compose
__fork_name__ = "PyroFork"
__version__ = "2.3.59"
__license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
__copyright__ = "Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>"
from concurrent.futures.thread import ThreadPoolExecutor
class StopTransmission(Exception):
pass
@ -36,9 +40,20 @@ class StopPropagation(StopAsyncIteration):
class ContinuePropagation(StopAsyncIteration):
pass
from . import raw, types, filters, handlers, emoji, enums
from .client import Client
from .sync import idle, compose
crypto_executor = ThreadPoolExecutor(1, thread_name_prefix="CryptoWorker")
__all__ = [
"Client",
"idle",
"compose",
"crypto_executor",
"StopTransmission",
"StopPropagation",
"ContinuePropagation",
"raw",
"types",
"filters",
"handlers",
"emoji",
"enums",
]

View file

@ -33,7 +33,7 @@ from importlib import import_module
from io import StringIO, BytesIO
from mimetypes import MimeTypes
from pathlib import Path
from typing import Union, List, Optional, Callable, AsyncGenerator, Type, Tuple
from typing import Union, List, Optional, Callable, AsyncGenerator, Tuple
import pyrogram
from pyrogram import __version__, __license__
@ -51,24 +51,26 @@ from pyrogram.handlers.handler import Handler
from pyrogram.methods import Methods
from pyrogram.session import Auth, Session
from pyrogram.storage import FileStorage, MemoryStorage, Storage
from pyrogram.types import User, TermsOfService
from pyrogram.utils import ainput
from .connection import Connection
from .connection.transport import TCPAbridged
from .dispatcher import Dispatcher
from .file_id import FileId, FileType, ThumbnailSource
from .mime_types import mime_types
from .parser import Parser
from .session.internals import MsgId
log = logging.getLogger(__name__)
MONGO_AVAIL = False
try:
import pymongo
except Exception:
pass
else:
from pyrogram.storage import MongoStorage
from pyrogram.types import User, TermsOfService
from pyrogram.utils import ainput
from .connection import Connection
from .connection.transport import TCP, TCPAbridged
from .dispatcher import Dispatcher
from .file_id import FileId, FileType, ThumbnailSource
from .filters import Filter
from .mime_types import mime_types
from .parser import Parser
from .session.internals import MsgId
log = logging.getLogger(__name__)
MONGO_AVAIL = True
class Client(Methods):
@ -316,9 +318,7 @@ class Client(Methods):
elif self.in_memory:
self.storage = MemoryStorage(self.name)
elif self.mongodb:
try:
import pymongo
except Exception:
if not MONGO_AVAIL:
log.warning(
"pymongo is missing! "
"Using MemoryStorage as session storage"
@ -953,7 +953,7 @@ class Client(Methods):
)
count += 1
except Exception as e:
except Exception:
pass
else:
for path, handlers in include:

View file

@ -23,3 +23,13 @@ from .tcp_abridged_o import TCPAbridgedO
from .tcp_full import TCPFull
from .tcp_intermediate import TCPIntermediate
from .tcp_intermediate_o import TCPIntermediateO
__all__ = [
"TCP",
"Proxy",
"TCPAbridged",
"TCPAbridgedO",
"TCPFull",
"TCPIntermediate",
"TCPIntermediateO"
]

View file

@ -71,7 +71,7 @@ def unpack(
message = Message.read(data)
except KeyError as e:
if e.args[0] == 0:
raise ConnectionError(f"Received empty data. Check your internet connection.")
raise ConnectionError("Received empty data. Check your internet connection.")
left = data.read().hex()
@ -79,7 +79,7 @@ def unpack(
left = [[left[i:i + 8] for i in range(0, len(left), 8)] for left in left]
left = "\n".join(" ".join(x for x in left) for left in left)
raise ValueError(f"The server sent an unknown constructor: {hex(e.args[0])}\n{left}")
raise ValueError("The server sent an unknown constructor: {hex(e.args[0])}\n{left}")
# https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key
# 96 = 88 + 8 (incoming message)

View file

@ -42,3 +42,31 @@ from .message_reaction_updated_handler import MessageReactionUpdatedHandler
from .message_reaction_count_updated_handler import MessageReactionCountUpdatedHandler
from .pre_checkout_query_handler import PreCheckoutQueryHandler
from .shipping_query_handler import ShippingQueryHandler
__all__ = [
"BotBusinessConnectHandler",
"BotBusinessMessageHandler",
"CallbackQueryHandler",
"ChatJoinRequestHandler",
"ChatMemberUpdatedHandler",
"ConversationHandler",
"ChosenInlineResultHandler",
"DeletedMessagesHandler",
"DeletedBotBusinessMessagesHandler",
"DisconnectHandler",
"EditedMessageHandler",
"EditedBotBusinessMessageHandler",
"ErrorHandler",
"InlineQueryHandler",
"MessageHandler",
"PollHandler",
"PreCheckoutQueryHandler",
"PurchasedPaidMediaHandler",
"RawUpdateHandler",
"UserStatusHandler",
"StoryHandler",
"MessageReactionUpdatedHandler",
"MessageReactionCountUpdatedHandler",
"PreCheckoutQueryHandler",
"ShippingQueryHandler",
]

View file

@ -101,9 +101,9 @@ def kb(rows=None, **kwargs):
line = []
for button in row:
button_type = type(button)
if button_type == str:
if isinstance(button_type, str):
button = KeyboardButton(button)
elif button_type == dict:
elif isinstance(button_type, dict):
button = KeyboardButton(**button)
line.append(button)

View file

@ -17,7 +17,6 @@
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from pyrogram import types
from typing import Union

View file

@ -16,7 +16,7 @@
# 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 Callable, Optional, Union
from typing import Callable
import pyrogram
from pyrogram.filters import Filter

View file

@ -180,8 +180,12 @@ class CopyMediaGroup:
**await self.parser.parse(
captions[i] if isinstance(captions, list) and i < len(captions) and captions[i] else
captions if isinstance(captions, str) and i == 0 else
message.caption if message.caption and message.caption != "None" and not type(
captions) is str else "")
message.caption if (
message.caption
and message.caption != "None"
and not isinstance(captions, str)
) else ""
)
)
)

View file

@ -18,7 +18,6 @@
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import html
import logging
import re
from typing import Optional

View file

@ -30,3 +30,24 @@ from .primitives.int import Int, Long, Int128, Int256
from .primitives.string import String
from .primitives.vector import Vector
from .tl_object import TLObject
__all__ = [
"FutureSalt",
"FutureSalts",
"GzipPacked",
"List",
"Message",
"MsgContainer",
"Bool",
"BoolFalse",
"BoolTrue",
"Bytes",
"Double",
"Int",
"Long",
"Int128",
"Int256",
"String",
"Vector",
"TLObject"
]

View file

@ -20,3 +20,9 @@
from .data_center import DataCenter
from .msg_factory import MsgFactory
from .msg_id import MsgId
__all__ = [
"DataCenter",
"MsgFactory",
"MsgId"
]

View file

@ -30,3 +30,8 @@ from .update import *
from .user_and_chats import *
from .payments import *
from .pyromod import *
__all__ = [
"List",
"Object"
]

View file

@ -18,8 +18,7 @@
from typing import List
import pyrogram
from pyrogram import raw, types, utils
from pyrogram import raw, types
from ..object import Object

View file

@ -17,14 +17,11 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from typing import Union, List, Match, Optional
import pyrogram
from pyrogram import raw, enums
from pyrogram import types
from pyrogram import raw, types
from ..object import Object
from ..update import Update
from ... import utils
class PreCheckoutQuery(Object, Update):
@ -76,7 +73,11 @@ class PreCheckoutQuery(Object, Update):
self.payment_info = payment_info
@staticmethod
async def _parse(client: "pyrogram.Client", pre_checkout_query, users) -> "PreCheckoutQuery":
async def _parse(
client: "pyrogram.Client",
pre_checkout_query: "raw.types.UpdateBotPrecheckoutQuery",
users
) -> "PreCheckoutQuery":
# Try to decode pre-checkout query payload into string. If that fails, fallback to bytes instead of decoding by
# ignoring/replacing errors, this way, button clicks will still work.
try:

View file

@ -1,5 +1,6 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrogram.
#

View file

@ -1,5 +1,6 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrogram.
#
@ -16,8 +17,6 @@
# 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 Union, Optional
import pyrogram
from pyrogram import raw, types
@ -49,14 +48,14 @@ class ShippingQuery(Object, Update):
client: "pyrogram.Client" = None,
id: str,
from_user: "types.User",
invoice_payload: str,
payload: str,
shipping_address: "types.ShippingAddress" = None
):
super().__init__(client)
self.id = id
self.from_user = from_user
self.invoice_payload = invoice_payload
self.payload = payload
self.shipping_address = shipping_address
@staticmethod
@ -72,19 +71,12 @@ class ShippingQuery(Object, Update):
except (UnicodeDecodeError, AttributeError):
payload = shipping_query.payload
return types.PreCheckoutQuery(
id=str(shipping_query.query_id),
from_user=types.User._parse(client, users[shipping_query.user_id]),
invoice_payload=payload,
shipping_address=types.ShippingAddress(
country_code=shipping_query.shipping_address.country_iso2,
state=shipping_query.shipping_address.state,
city=shipping_query.shipping_address.city,
street_line1=shipping_query.shipping_address.street_line1,
street_line2=shipping_query.shipping_address.street_line2,
post_code=shipping_query.shipping_address.post_code
),
client=client
return ShippingQuery(
client=client,
id=shipping_query.query_id,
from_user=types.User._parse(client, shipping_query.user_id, users),
payload=payload,
shipping_address=types.ShippingAddress._parse(client, shipping_query.shipping_address)
)
async def answer(

View file

@ -27,6 +27,7 @@ from .contact import Contact
from .contact_registered import ContactRegistered
from .dice import Dice
from .document import Document
from .external_reply_info import ExternalReplyInfo
from .game import Game
from .giveaway import Giveaway
from .giveaway_launched import GiveawayLaunched
@ -89,6 +90,7 @@ __all__ = [
"Contact",
"ContactRegistered",
"Document",
"ExternalReplyInfo",
"Game",
"Giveaway",
"GiveawayLaunched",

View file

@ -0,0 +1,319 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# 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.
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,
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.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(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, users)
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,
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
@staticmethod
def _parse(client, message: "raw.types.Message") -> "Game":
game: "raw.types.Game" = message.media.game
def _parse(client, media: "raw.types.MessageMediaGame") -> "Game":
animation = None
if game.document:
attributes = {type(i): i for i in game.document.attributes}
if media.game.document:
attributes = {type(i): i for i in media.game.document.attributes}
file_name = getattr(
attributes.get(
@ -84,17 +83,17 @@ class Game(Object):
animation = types.Animation._parse(
client,
game.document,
media.game.document,
attributes.get(raw.types.DocumentAttributeVideo, None),
file_name
)
return Game(
id=game.id,
title=game.title,
short_name=game.short_name,
description=game.description,
photo=types.Photo._parse(client, game.photo),
id=media.game.id,
title=media.game.title,
short_name=media.game.short_name,
description=media.game.description,
photo=types.Photo._parse(client, media.game.photo),
animation=animation,
client=client
)

View file

@ -73,7 +73,6 @@ class Giveaway(Object):
stars: int = None,
additional_price: str = None,
allowed_countries: List[str] = None,
private_channel_ids: List[int] = None,
is_winners_hidden: bool = None
):
super().__init__(client)
@ -86,28 +85,12 @@ class Giveaway(Object):
self.new_subscribers = new_subscribers
self.additional_price = additional_price
self.allowed_countries = allowed_countries
self.private_channel_ids = private_channel_ids
self.is_winners_hidden = is_winners_hidden
@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
chats = []
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]))
chats = types.List([types.Chat._parse_channel_chat(client, chats.get(i)) for i in giveaway.channels])
return Giveaway(
chats=chats,
@ -118,7 +101,6 @@ class Giveaway(Object):
new_subscribers=giveaway.only_new_subscribers,
additional_price=giveaway.prize_description,
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,
client=client
)

View file

@ -318,6 +318,9 @@ class Message(Object, Update):
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.
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*):
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>`.
@ -507,6 +510,7 @@ class Message(Object, Update):
forwards: int = None,
via_bot: "types.User" = None,
outgoing: bool = None,
external_reply: Optional["types.ExternalReplyInfo"] = None,
matches: List[Match] = None,
command: List[str] = None,
bot_allowed: "types.BotAllowed" = None,
@ -546,8 +550,8 @@ class Message(Object, Update):
self.sender_business_bot = sender_business_bot
self.date = date
self.chat = chat
self.topic = topic
self.forward_origin = forward_origin
self.external_reply = external_reply
self.is_topic_message = is_topic_message
self.reply_to_chat_id = reply_to_chat_id
self.reply_to_message_id = reply_to_message_id
@ -1032,10 +1036,10 @@ class Message(Object, Update):
venue = types.Venue._parse(client, media)
media_type = enums.MessageMediaType.VENUE
elif isinstance(media, raw.types.MessageMediaGame):
game = types.Game._parse(client, message)
game = types.Game._parse(client, media)
media_type = enums.MessageMediaType.GAME
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
elif isinstance(media, raw.types.MessageMediaGiveawayResults):
giveaway_result = await types.GiveawayResult._parse(client, message.media)
@ -1221,6 +1225,12 @@ class Message(Object, Update):
parsed_message.sender_chat = sender_chat
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 message.reply_to.quote:
parsed_message.quote = types.TextQuote._parse(
@ -1263,20 +1273,15 @@ class Message(Object, Update):
if replies:
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:
key = (parsed_message.chat.id, parsed_message.reply_to_message_id)
reply_to_message = client.message_cache[key]
if not reply_to_message:
reply_to_message = await client.get_messages(
replies=replies - 1,
**reply_to_params
parsed_message.chat.id,
reply_to_message_ids=message.id,
replies=replies - 1
)
if reply_to_message and not reply_to_message.forum_topic_created:
parsed_message.reply_to_message = reply_to_message

View file

@ -16,11 +16,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from typing import List, Optional, Union
import pyrogram
from pyrogram import raw
from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType, ThumbnailSource
from ..object import Object

View file

@ -57,7 +57,7 @@ class TranscribedAudio(Object):
self.trial_remains_until_date = trial_remains_until_date
@staticmethod
def _parse(transcribe_result: "raw.types.messages.TranscribedAudio") -> "TranscribeAudio":
def _parse(transcribe_result: "raw.types.messages.TranscribedAudio") -> "TranscribedAudio":
return TranscribedAudio(
transcription_id=transcribe_result.transcription_id,
text=transcribe_result.text,

View file

@ -16,8 +16,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from ..object import Object

View file

@ -121,9 +121,14 @@ async def parse_messages(
if replies:
messages_with_replies = {
i.id: i.reply_to
i.id: i.reply_to.reply_to_msg_id
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 = {
@ -136,61 +141,27 @@ 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, types.Message):
continue
if m.chat:
chat_id = m.chat.id
break
else:
chat_id = 0
is_all_within_chat = not any(
value.reply_to_peer_id
for value in messages_with_replies.values()
reply_messages = await client.get_messages(
chat_id,
reply_to_message_ids=messages_with_replies.keys(),
replies=replies - 1
)
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(
chat_id,
reply_to_message_ids=messages_with_replies.keys(),
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:
reply_to = messages_with_replies.get(message.id, None)
if not reply_to:
continue
reply_id = reply_to.reply_to_msg_id
reply_id = messages_with_replies.get(message.id, None)
for reply in reply_messages:
if reply.id == reply_id and not reply.forum_topic_created:
message.reply_to_message = reply
if reply.id == reply_id:
if not reply.forum_topic_created:
message.reply_to_message = reply
if message_reply_to_story:
for m in parsed_messages:
if not isinstance(m, types.Message):
continue
if m.chat:
chat_id = m.chat.id
break
@ -198,7 +169,7 @@ async def parse_messages(
chat_id = 0
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(
message_reply_to_story[msg_id]['user_id'],
message_reply_to_story[msg_id]['story_id']