Compare commits

...

6 commits

Author SHA1 Message Date
wulan17
407e75316b
pyrofork: fix NoneType exception in filters
Some checks failed
Build-docs / build (push) Has been cancelled
Pyrofork / build (macos-latest, 3.10) (push) Has been cancelled
Pyrofork / build (macos-latest, 3.11) (push) Has been cancelled
Pyrofork / build (macos-latest, 3.12) (push) Has been cancelled
Pyrofork / build (macos-latest, 3.13) (push) Has been cancelled
Pyrofork / build (macos-latest, 3.9) (push) Has been cancelled
Pyrofork / build (ubuntu-latest, 3.10) (push) Has been cancelled
Pyrofork / build (ubuntu-latest, 3.11) (push) Has been cancelled
Pyrofork / build (ubuntu-latest, 3.12) (push) Has been cancelled
Pyrofork / build (ubuntu-latest, 3.13) (push) Has been cancelled
Pyrofork / build (ubuntu-latest, 3.9) (push) Has been cancelled
Signed-off-by: wulan17 <wulan17@komodos.id>
2025-06-25 19:10:50 +07:00
KurimuzonAkuma
99801df191
Add get_call_members method
Some checks failed
Build-docs / build (push) Has been cancelled
Pyrofork / build (macos-latest, 3.10) (push) Has been cancelled
Pyrofork / build (macos-latest, 3.11) (push) Has been cancelled
Pyrofork / build (macos-latest, 3.12) (push) Has been cancelled
Pyrofork / build (macos-latest, 3.13) (push) Has been cancelled
Pyrofork / build (macos-latest, 3.9) (push) Has been cancelled
Pyrofork / build (ubuntu-latest, 3.10) (push) Has been cancelled
Pyrofork / build (ubuntu-latest, 3.11) (push) Has been cancelled
Pyrofork / build (ubuntu-latest, 3.12) (push) Has been cancelled
Pyrofork / build (ubuntu-latest, 3.13) (push) Has been cancelled
Pyrofork / build (ubuntu-latest, 3.9) (push) Has been cancelled
Signed-off-by: gudmeong <privatemymail758@gmail.com>
Signed-off-by: wulan17 <wulan17@komodos.id>
2025-06-21 19:49:32 +07:00
wulan17
f59f9710b9
pyrofork: Add back reverse parameter in get_chat_history
Signed-off-by: wulan17 <wulan17@komodos.id>
2025-06-21 19:47:25 +07:00
KurimuzonAkuma
b1a990cf97
Update upgraded gift regex
Signed-off-by: wulan17 <wulan17@komodos.id>
2025-06-21 19:34:40 +07:00
wulan17
bfa68ec079
pyrofork: fix typo in KeyboardButton
Signed-off-by: wulan17 <wulan17@komodos.id>
2025-06-21 19:26:45 +07:00
wulan17
635804f81b
pyrofork: disable publish workflows
Signed-off-by: wulan17 <wulan17@nusantararom.org>
2025-06-21 19:24:02 +07:00
13 changed files with 306 additions and 51 deletions

View file

@ -1,40 +0,0 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Upload Python Package
on:
push:
tags:
- '*'
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e '.[dev]'
- name: Build package
run: hatch build
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1

View file

@ -379,6 +379,10 @@ def pyrogram_api():
upgrade_gift upgrade_gift
get_stars_balance get_stars_balance
""", """,
phone="""
Phone
get_call_members
""",
password=""" password="""
Password Password
enable_cloud_password enable_cloud_password
@ -514,6 +518,7 @@ def pyrogram_api():
PeerUser PeerUser
PeerChannel PeerChannel
BotInfo BotInfo
GroupCallMember
ChatColor ChatColor
CollectibleItemInfo CollectibleItemInfo
BotVerification BotVerification

View file

@ -231,6 +231,7 @@ class Client(Methods):
PARENT_DIR = Path(sys.argv[0]).parent PARENT_DIR = Path(sys.argv[0]).parent
INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:joinchat/|\+))([\w-]+)$") INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:joinchat/|\+))([\w-]+)$")
UPGRADED_GIFT_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:nft/|\+))([\w-]+)$")
WORKERS = min(32, (os.cpu_count() or 0) + 4) # os.cpu_count() can be None WORKERS = min(32, (os.cpu_count() or 0) + 4) # os.cpu_count() can be None
WORKDIR = PARENT_DIR WORKDIR = PARENT_DIR

View file

@ -1035,7 +1035,7 @@ class user(Filter, set):
async def __call__(self, _, message: Message): async def __call__(self, _, message: Message):
is_usernames_in_filters = False is_usernames_in_filters = False
if message.from_user.usernames: if message.from_user and message.from_user.usernames:
for username in message.from_user.usernames: for username in message.from_user.usernames:
if ( if (
username.username in self username.username in self
@ -1078,7 +1078,7 @@ class chat(Filter, set):
async def __call__(self, _, message: Union[Message, Story]): async def __call__(self, _, message: Union[Message, Story]):
if isinstance(message, Story): if isinstance(message, Story):
is_usernames_in_filters = False is_usernames_in_filters = False
if message.sender_chat.usernames: if message.sender_chat and message.sender_chat.usernames:
for username in message.sender_chat.usernames: for username in message.sender_chat.usernames:
if ( if (
username.username in self username.username in self
@ -1107,7 +1107,7 @@ class chat(Filter, set):
) or is_usernames_in_filters ) or is_usernames_in_filters
else: else:
is_usernames_in_filters = False is_usernames_in_filters = False
if message.chat.usernames: if message.chat and message.chat.usernames:
for username in message.chat.usernames: for username in message.chat.usernames:
if ( if (
username.username in self username.username in self

View file

@ -29,6 +29,7 @@ from .password import Password
from .pyromod import Pyromod from .pyromod import Pyromod
from .stickers import Stickers from .stickers import Stickers
from .payments import Payments from .payments import Payments
from .phone import Phone
from .users import Users from .users import Users
from .utilities import Utilities from .utilities import Utilities
from .business import TelegramBusiness from .business import TelegramBusiness
@ -42,6 +43,7 @@ class Methods(
Password, Password,
Pyromod, Pyromod,
Payments, Payments,
Phone,
Chats, Chats,
Stickers, Stickers,
Users, Users,

View file

@ -33,7 +33,8 @@ async def get_chunk(
from_message_id: int = 0, from_message_id: int = 0,
from_date: datetime = utils.zero_datetime(), from_date: datetime = utils.zero_datetime(),
min_id: int = 0, min_id: int = 0,
max_id: int = 0 max_id: int = 0,
reverse: Optional[bool] = None
): ):
messages = await client.invoke( messages = await client.invoke(
raw.functions.messages.GetHistory( raw.functions.messages.GetHistory(
@ -48,6 +49,8 @@ async def get_chunk(
), ),
sleep_threshold=60 sleep_threshold=60
) )
if reverse:
messages.messages.reverse()
return await utils.parse_messages(client, messages, replies=0) return await utils.parse_messages(client, messages, replies=0)
@ -61,7 +64,8 @@ class GetChatHistory:
offset_id: int = 0, offset_id: int = 0,
offset_date: datetime = utils.zero_datetime(), offset_date: datetime = utils.zero_datetime(),
min_id: int = 0, min_id: int = 0,
max_id: int = 0 max_id: int = 0,
reverse: Optional[bool] = None
) -> Optional[AsyncGenerator["types.Message", None]]: ) -> Optional[AsyncGenerator["types.Message", None]]:
"""Get messages from a chat history. """Get messages from a chat history.
@ -96,6 +100,9 @@ class GetChatHistory:
max_id: (``int``, *optional*): max_id: (``int``, *optional*):
The maximum message id. you will not get any message which have id greater than max_id. The maximum message id. you will not get any message which have id greater than max_id.
reverse (``bool``, *optional*):
Pass True to retrieve the messages in reversed order (from older to most recent).
Returns: Returns:
``Generator``: A generator yielding :obj:`~pyrogram.types.Message` objects. ``Generator``: A generator yielding :obj:`~pyrogram.types.Message` objects.
@ -118,7 +125,8 @@ class GetChatHistory:
from_message_id=offset_id, from_message_id=offset_id,
from_date=offset_date, from_date=offset_date,
min_id=min_id, min_id=min_id,
max_id=max_id max_id=max_id,
reverse=reverse
) )
if not messages: if not messages:

View file

@ -47,7 +47,7 @@ class GetUpgradedGift:
# Get information about upgraded gift by slug # Get information about upgraded gift by slug
gift = await client.get_upgraded_gift("SignetRing-903") gift = await client.get_upgraded_gift("SignetRing-903")
""" """
match = re.match(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:nft/|\+))([\w-]+)$", link) match = self.UPGRADED_GIFT_RE.match(link)
if match: if match:
slug = match.group(1) slug = match.group(1)

View file

@ -62,8 +62,8 @@ class SetPinnedGifts:
if not isinstance(gift, str): if not isinstance(gift, str):
raise ValueError(f"gift id has to be str, but {type(gift)} was provided") raise ValueError(f"gift id has to be str, but {type(gift)} was provided")
saved_gift_match = re.match(r"^(-\d+)_(\d+)$", gift) saved_gift_match = re.match(r"^(\d+)_(\d+)$", str(gift))
slug_match = self.UPGRADED_GIFT_RE.match(gift) slug_match = self.UPGRADED_GIFT_RE.match(str(gift))
if saved_gift_match: if saved_gift_match:
stargifts.append( stargifts.append(

View file

@ -0,0 +1,25 @@
# 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 .get_call_members import GetCallMembers
class Phone(
GetCallMembers
):
pass

View file

@ -0,0 +1,103 @@
# 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 Union, AsyncGenerator
import pyrogram
from pyrogram import types, raw
class GetCallMembers:
async def get_call_members(
self: "pyrogram.Client",
chat_id: Union[int, str],
limit: int = 0
) -> AsyncGenerator["types.GroupCallMember", None]:
"""Get the members list of a chat call.
A chat can be either a basic group or a supergroup.
.. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
limit (``int``, *optional*):
Limits the number of members to be retrieved.
Returns:
``Generator``: On success, a generator yielding :obj:`~pyrogram.types.GroupCallMember` objects is returned.
Example:
.. code-block:: python
# Get members
async for member in app.get_call_members(chat_id):
print(member)
"""
peer = await self.resolve_peer(chat_id)
if isinstance(peer, raw.types.InputPeerChannel):
r = await self.invoke(raw.functions.channels.GetFullChannel(channel=peer))
elif isinstance(peer, raw.types.InputPeerChat):
r = await self.invoke(raw.functions.messages.GetFullChat(chat_id=peer.chat_id))
else:
raise ValueError("Target chat should be group, supergroup or channel.")
full_chat = r.full_chat
if not getattr(full_chat, "call", None):
raise ValueError("There is no active call in this chat.")
current = 0
offset = ""
total = abs(limit) or (1 << 31) - 1
limit = min(20, total)
while True:
r = await self.invoke(
raw.functions.phone.GetGroupParticipants(
call=full_chat.call,
ids=[],
sources=[],
offset=offset,
limit=limit
),
sleep_threshold=60
)
users = {u.id: u for u in r.users}
chats = {c.id: c for c in r.chats}
members = [
types.GroupCallMember._parse(self, member, users, chats)
for member in r.participants
]
if not members:
return
offset = r.next_offset
for member in members:
yield member
current += 1
if current >= total:
return

View file

@ -143,8 +143,8 @@ class KeyboardButton(Object):
elif self.request_location: elif self.request_location:
return raw.types.KeyboardButtonRequestGeoLocation(text=self.text) return raw.types.KeyboardButtonRequestGeoLocation(text=self.text)
elif self.request_chat: elif self.request_chat:
user_privileges = self.request_peer.user_privileges user_privileges = self.request_chat.user_privileges
bot_privileges = self.request_peer.bot_privileges bot_privileges = self.request_chat.bot_privileges
user_admin_rights = raw.types.ChatAdminRights( user_admin_rights = raw.types.ChatAdminRights(
change_info=user_privileges.can_change_info, change_info=user_privileges.can_change_info,

View file

@ -43,6 +43,7 @@ from .chat_reactions import ChatReactions
from .dialog import Dialog from .dialog import Dialog
from .emoji_status import EmojiStatus from .emoji_status import EmojiStatus
from .folder import Folder from .folder import Folder
from .group_call_member import GroupCallMember
from .invite_link_importer import InviteLinkImporter from .invite_link_importer import InviteLinkImporter
from .restriction import Restriction from .restriction import Restriction
from .user import User from .user import User
@ -106,5 +107,6 @@ __all__ = [
"ChatPrivileges", "ChatPrivileges",
"ChatJoiner", "ChatJoiner",
"EmojiStatus", "EmojiStatus",
"GroupCallMember",
"ChatReactions" "ChatReactions"
] ]

View file

@ -0,0 +1,149 @@
# 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 datetime import datetime
from typing import Dict
import pyrogram
from pyrogram import raw, types, utils
from ..object import Object
class GroupCallMember(Object):
"""Contains information about one member of a group call.
Parameters:
chat (:obj:`~pyrogram.types.Chat`, *optional*):
Information about the user or chat.
date (:py:obj:`~datetime.datetime`, *optional*):
Date when this participant join this group call.
active_date (:py:obj:`~datetime.datetime`, *optional*):
Date when this participant last active in this group call.
volume (``int``, *optional*):
Volume, if not set the volume is set to 100%.
can_self_unmute (``bool``, *optional*):
Whether the participant can unmute themselves.
is_muted (``bool``, *optional*):
Whether the participant is muted.
is_left (``bool``, *optional*):
Whether the participant has left.
is_just_joined (``bool``, *optional*):
Whether the participant has just joined.
is_muted_by_you (``bool``, *optional*):
Whether this participant was muted by the current user.
is_volume_by_admin (``bool``, *optional*):
Whether our volume can only changed by an admin.
is_self (``bool``, *optional*):
Whether this participant is the current user.
is_video_joined (``bool``, *optional*):
Whether this participant is currently broadcasting video.
is_hand_raised (``bool``, *optional*):
Whether this participant is raised hand.
is_video_enabled (``bool``, *optional*):
Whether this participant is currently broadcasting video.
is_screen_sharing_enabled (``bool``, *optional*):
Whether this participant is currently shared screen.
"""
def __init__(
self,
*,
client: "pyrogram.Client" = None,
chat: "types.Chat" = None,
date: datetime = None,
active_date: datetime = None,
volume: int = None,
can_self_unmute: bool = None,
is_muted: bool = None,
is_left: bool = None,
is_just_joined: bool = None,
is_muted_by_you: bool = None,
is_volume_by_admin: bool = None,
is_self: bool = None,
is_video_joined: bool = None,
is_hand_raised: bool = None,
is_video_enabled: bool = None,
is_screen_sharing_enabled: bool = None
):
super().__init__(client)
self.chat = chat
self.date = date
self.active_date = active_date
self.volume = volume
self.can_self_unmute = can_self_unmute
self.is_muted = is_muted
self.is_left = is_left
self.is_just_joined = is_just_joined
self.is_muted_by_you = is_muted_by_you
self.is_volume_by_admin = is_volume_by_admin
self.is_self = is_self
self.is_video_joined = is_video_joined
self.is_hand_raised = is_hand_raised
self.is_video_enabled = is_video_enabled
self.is_screen_sharing_enabled = is_screen_sharing_enabled
@staticmethod
def _parse(
client: "pyrogram.Client",
member: "raw.types.GroupCallParticipant",
users: Dict[int, "raw.base.User"],
chats: Dict[int, "raw.base.Chat"]
) -> "GroupCallMember":
peer = member.peer
peer_id = utils.get_raw_peer_id(peer)
parsed_chat = types.Chat._parse_chat(
client,
users[peer_id] if isinstance(peer, raw.types.PeerUser) else chats[peer_id],
)
parsed_chat.bio = getattr(member, "about", None)
return GroupCallMember(
chat=parsed_chat,
date=utils.timestamp_to_datetime(member.date),
active_date=utils.timestamp_to_datetime(member.active_date),
volume=getattr(member, "volume", None),
can_self_unmute=member.can_self_unmute,
is_muted=member.muted,
is_left=member.left,
is_just_joined=member.just_joined,
is_muted_by_you=member.muted_by_you,
is_volume_by_admin=member.volume_by_admin,
is_self=member.is_self,
is_video_joined=member.video_joined,
is_hand_raised=bool(getattr(member, "raise_hand_rating", None)),
is_video_enabled=bool(getattr(member, "video", None)),
is_screen_sharing_enabled=bool(getattr(member, "presentation", None)),
client=client
)