mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-08 12:59:46 +00:00
Continue implementation and documentation
This commit is contained in:
@@ -3,8 +3,9 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from ...tl import abcs, functions, types
|
||||
from ..types import AsyncList, ChatLike, Participant
|
||||
from ..types import AsyncList, ChatLike, File, Participant, RecentAction
|
||||
from ..utils import build_chat_map
|
||||
from .messages import SearchList
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .client import Client
|
||||
@@ -81,3 +82,106 @@ class ParticipantList(AsyncList[Participant]):
|
||||
|
||||
def get_participants(self: Client, chat: ChatLike) -> AsyncList[Participant]:
|
||||
return ParticipantList(self, chat)
|
||||
|
||||
|
||||
class RecentActionList(AsyncList[RecentAction]):
|
||||
def __init__(
|
||||
self,
|
||||
client: Client,
|
||||
chat: ChatLike,
|
||||
):
|
||||
super().__init__()
|
||||
self._client = client
|
||||
self._chat = chat
|
||||
self._peer: Optional[types.InputChannel] = None
|
||||
self._offset = 0
|
||||
|
||||
async def _fetch_next(self) -> None:
|
||||
if self._peer is None:
|
||||
self._peer = (
|
||||
await self._client._resolve_to_packed(self._chat)
|
||||
)._to_input_channel()
|
||||
|
||||
result = await self._client(
|
||||
functions.channels.get_admin_log(
|
||||
channel=self._peer,
|
||||
q="",
|
||||
min_id=0,
|
||||
max_id=self._offset,
|
||||
limit=100,
|
||||
events_filter=None,
|
||||
admins=[],
|
||||
)
|
||||
)
|
||||
assert isinstance(result, types.channels.AdminLogResults)
|
||||
|
||||
chat_map = build_chat_map(result.users, result.chats)
|
||||
self._buffer.extend(RecentAction._create(e, chat_map) for e in result.events)
|
||||
self._total += len(self._buffer)
|
||||
|
||||
if self._buffer:
|
||||
self._offset = min(e.id for e in self._buffer)
|
||||
|
||||
|
||||
def get_admin_log(self: Client, chat: ChatLike) -> AsyncList[RecentAction]:
|
||||
return RecentActionList(self, chat)
|
||||
|
||||
|
||||
class ProfilePhotoList(AsyncList[File]):
|
||||
def __init__(
|
||||
self,
|
||||
client: Client,
|
||||
chat: ChatLike,
|
||||
):
|
||||
super().__init__()
|
||||
self._client = client
|
||||
self._chat = chat
|
||||
self._peer: Optional[abcs.InputPeer] = None
|
||||
self._search_iter: Optional[SearchList] = None
|
||||
|
||||
async def _fetch_next(self) -> None:
|
||||
if self._peer is None:
|
||||
self._peer = (
|
||||
await self._client._resolve_to_packed(self._chat)
|
||||
)._to_input_peer()
|
||||
|
||||
if isinstance(self._peer, types.InputPeerUser):
|
||||
result = await self._client(
|
||||
functions.photos.get_user_photos(
|
||||
user_id=types.InputUser(
|
||||
user_id=self._peer.user_id, access_hash=self._peer.access_hash
|
||||
),
|
||||
offset=0,
|
||||
max_id=0,
|
||||
limit=0,
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(result, types.photos.Photos):
|
||||
self._buffer.extend(
|
||||
filter(None, (File._try_from_raw_photo(p) for p in result.photos))
|
||||
)
|
||||
self._total = len(result.photos)
|
||||
elif isinstance(result, types.photos.PhotosSlice):
|
||||
self._buffer.extend(
|
||||
filter(None, (File._try_from_raw_photo(p) for p in result.photos))
|
||||
)
|
||||
self._total = result.count
|
||||
else:
|
||||
raise RuntimeError("unexpected case")
|
||||
|
||||
|
||||
def get_profile_photos(self: Client, chat: ChatLike) -> AsyncList[File]:
|
||||
return ProfilePhotoList(self, chat)
|
||||
|
||||
|
||||
def set_banned_rights(self: Client, chat: ChatLike, user: ChatLike) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def set_admin_rights(self: Client, chat: ChatLike, user: ChatLike) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def set_default_rights(self: Client, chat: ChatLike, user: ChatLike) -> None:
|
||||
pass
|
||||
|
@@ -36,6 +36,7 @@ from ..types import (
|
||||
Chat,
|
||||
ChatLike,
|
||||
Dialog,
|
||||
Draft,
|
||||
File,
|
||||
InFileLike,
|
||||
LoginToken,
|
||||
@@ -43,6 +44,7 @@ from ..types import (
|
||||
OutFileLike,
|
||||
Participant,
|
||||
PasswordToken,
|
||||
RecentAction,
|
||||
User,
|
||||
)
|
||||
from .auth import (
|
||||
@@ -55,8 +57,15 @@ from .auth import (
|
||||
sign_out,
|
||||
)
|
||||
from .bots import InlineResult, inline_query
|
||||
from .chats import get_participants
|
||||
from .dialogs import delete_dialog, get_dialogs
|
||||
from .chats import (
|
||||
get_admin_log,
|
||||
get_participants,
|
||||
get_profile_photos,
|
||||
set_admin_rights,
|
||||
set_banned_rights,
|
||||
set_default_rights,
|
||||
)
|
||||
from .dialogs import delete_dialog, get_dialogs, get_drafts
|
||||
from .files import (
|
||||
download,
|
||||
get_file_bytes,
|
||||
@@ -198,7 +207,7 @@ class Client:
|
||||
if self._config.catch_up and self._config.session.state:
|
||||
self._message_box.load(self._config.session.state)
|
||||
|
||||
# ---
|
||||
# Begin partially @generated
|
||||
|
||||
def add_event_handler(
|
||||
self,
|
||||
@@ -511,6 +520,29 @@ class Client:
|
||||
"""
|
||||
return await forward_messages(self, target, message_ids, source)
|
||||
|
||||
def get_admin_log(self, chat: ChatLike) -> AsyncList[RecentAction]:
|
||||
"""
|
||||
Get the recent actions from the administrator's log.
|
||||
|
||||
This method requires you to be an administrator in the :term:`chat`.
|
||||
|
||||
The returned actions are also known as "admin log events".
|
||||
|
||||
:param chat:
|
||||
The :term:`chat` to fetch recent actions from.
|
||||
|
||||
:return: The recent actions.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async for admin_log_event in client.get_admin_log(chat):
|
||||
if message := admin_log_event.deleted_message:
|
||||
print('Deleted:', message.text)
|
||||
"""
|
||||
return get_admin_log(self, chat)
|
||||
|
||||
def get_contacts(self) -> AsyncList[User]:
|
||||
"""
|
||||
Get the users in your contact list.
|
||||
@@ -548,6 +580,47 @@ class Client:
|
||||
"""
|
||||
return get_dialogs(self)
|
||||
|
||||
def get_drafts(self) -> AsyncList[Draft]:
|
||||
"""
|
||||
Get all message drafts saved in any dialog.
|
||||
|
||||
:return: The existing message drafts.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async for draft in client.get_drafts():
|
||||
await draft.delete()
|
||||
"""
|
||||
return get_drafts(self)
|
||||
|
||||
def get_file_bytes(self, media: File) -> AsyncList[bytes]:
|
||||
"""
|
||||
Get the contents of an uploaded media file as chunks of :class:`bytes`.
|
||||
|
||||
This lets you iterate over the chunks of a file and print progress while the download occurs.
|
||||
|
||||
If you just want to download a file to disk without printing progress, use :meth:`download` instead.
|
||||
|
||||
:param media:
|
||||
The media file to download.
|
||||
This will often come from :attr:`telethon.types.Message.file`.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if file := message.file:
|
||||
with open(f'media{file.ext}', 'wb') as fd:
|
||||
downloaded = 0
|
||||
async for chunk in client.get_file_bytes(file):
|
||||
downloaded += len(chunk)
|
||||
fd.write(chunk)
|
||||
print(f'Downloaded {downloaded // 1024}/{file.size // 1024} KiB')
|
||||
"""
|
||||
return get_file_bytes(self, media)
|
||||
|
||||
def get_handler_filter(
|
||||
self, handler: Callable[[Event], Awaitable[Any]]
|
||||
) -> Optional[Filter]:
|
||||
@@ -666,6 +739,23 @@ class Client:
|
||||
"""
|
||||
return get_participants(self, chat)
|
||||
|
||||
def get_profile_photos(self, chat: ChatLike) -> AsyncList[File]:
|
||||
"""
|
||||
Get the profile pictures set in a chat, or user avatars.
|
||||
|
||||
:return: The photo files.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
i = 0
|
||||
async for photo in client.get_profile_photos(chat):
|
||||
await client.download(photo, f'{i}.jpg')
|
||||
i += 1
|
||||
"""
|
||||
return get_profile_photos(self, chat)
|
||||
|
||||
async def inline_query(
|
||||
self, bot: ChatLike, query: str = "", *, chat: Optional[ChatLike] = None
|
||||
) -> AsyncIterator[InlineResult]:
|
||||
@@ -730,34 +820,15 @@ class Client:
|
||||
Check whether the client instance is authorized (i.e. logged-in).
|
||||
|
||||
:return: :data:`True` if the client instance has signed-in.
|
||||
"""
|
||||
return await is_authorized(self)
|
||||
|
||||
def get_file_bytes(self, media: File) -> AsyncList[bytes]:
|
||||
"""
|
||||
Get the contents of an uploaded media file as chunks of :class:`bytes`.
|
||||
|
||||
This lets you iterate over the chunks of a file and print progress while the download occurs.
|
||||
|
||||
If you just want to download a file to disk without printing progress, use :meth:`download` instead.
|
||||
|
||||
:param media:
|
||||
The media file to download.
|
||||
This will often come from :attr:`telethon.types.Message.file`.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if file := message.file:
|
||||
with open(f'media{file.ext}', 'wb') as fd:
|
||||
downloaded = 0
|
||||
async for chunk in client.get_file_bytes(file):
|
||||
downloaded += len(chunk)
|
||||
fd.write(chunk)
|
||||
print(f'Downloaded {downloaded // 1024}/{file.size // 1024} KiB')
|
||||
if not await client.is_authorized():
|
||||
... # need to sign in
|
||||
"""
|
||||
return get_file_bytes(self, media)
|
||||
return await is_authorized(self)
|
||||
|
||||
def on(
|
||||
self, event_cls: Type[Event], filter: Optional[Filter] = None
|
||||
@@ -934,6 +1005,13 @@ class Client:
|
||||
This means only messages sent before *offset_date* will be fetched.
|
||||
|
||||
:return: The found messages.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async for message in client.search_all_messages(query='hello'):
|
||||
print(message.text)
|
||||
"""
|
||||
return search_all_messages(
|
||||
self, limit, query=query, offset_id=offset_id, offset_date=offset_date
|
||||
@@ -970,6 +1048,13 @@ class Client:
|
||||
This means only messages sent before *offset_date* will be fetched.
|
||||
|
||||
:return: The found messages.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async for message in client.search_messages(chat, query='hello'):
|
||||
print(message.text)
|
||||
"""
|
||||
return search_messages(
|
||||
self, chat, limit, query=query, offset_id=offset_id, offset_date=offset_date
|
||||
@@ -988,6 +1073,9 @@ class Client:
|
||||
voice: bool = False,
|
||||
title: Optional[str] = None,
|
||||
performer: Optional[str] = None,
|
||||
caption: Optional[str] = None,
|
||||
caption_markdown: Optional[str] = None,
|
||||
caption_html: Optional[str] = None,
|
||||
) -> Message:
|
||||
"""
|
||||
Send an audio file.
|
||||
@@ -1002,6 +1090,12 @@ class Client:
|
||||
A local file path or :class:`~telethon.types.File` to send.
|
||||
|
||||
The rest of parameters behave the same as they do in :meth:`send_file`.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
await client.send_audio(chat, 'file.ogg', voice=True)
|
||||
"""
|
||||
return await send_audio(
|
||||
self,
|
||||
@@ -1015,6 +1109,9 @@ class Client:
|
||||
voice=voice,
|
||||
title=title,
|
||||
performer=performer,
|
||||
caption=caption,
|
||||
caption_markdown=caption_markdown,
|
||||
caption_html=caption_html,
|
||||
)
|
||||
|
||||
async def send_file(
|
||||
@@ -1072,7 +1169,16 @@ class Client:
|
||||
if *path* isn't a :class:`~telethon.types.File`.
|
||||
See the documentation of :meth:`~telethon.types.File.new` to learn what they do.
|
||||
|
||||
See the section on :doc:`/concepts/messages` to learn about message formatting.
|
||||
|
||||
Note that only one *caption* parameter can be provided.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
login_token = await client.request_login_code('+1 23 456...')
|
||||
print(login_token.timeout, 'seconds before code expires')
|
||||
"""
|
||||
return await send_file(
|
||||
self,
|
||||
@@ -1120,20 +1226,23 @@ class Client:
|
||||
Message text, with no formatting.
|
||||
|
||||
:param text_markdown:
|
||||
Message text, parsed as markdown.
|
||||
Message text, parsed as CommonMark.
|
||||
|
||||
:param text_html:
|
||||
Message text, parsed as HTML.
|
||||
|
||||
Note that exactly one *text* parameter must be provided.
|
||||
|
||||
See the section on :doc:`/concepts/messages` to learn about message formatting.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
await client.send_message(chat, markdown='**Hello!**')
|
||||
"""
|
||||
return await send_message(
|
||||
self,
|
||||
chat,
|
||||
text,
|
||||
markdown=markdown,
|
||||
html=html,
|
||||
link_preview=link_preview,
|
||||
self, chat, text, markdown=markdown, html=html, link_preview=link_preview
|
||||
)
|
||||
|
||||
async def send_photo(
|
||||
@@ -1148,6 +1257,9 @@ class Client:
|
||||
compress: bool = True,
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
caption: Optional[str] = None,
|
||||
caption_markdown: Optional[str] = None,
|
||||
caption_html: Optional[str] = None,
|
||||
) -> Message:
|
||||
"""
|
||||
Send a photo file.
|
||||
@@ -1166,6 +1278,12 @@ class Client:
|
||||
A local file path or :class:`~telethon.types.File` to send.
|
||||
|
||||
The rest of parameters behave the same as they do in :meth:`send_file`.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
await client.send_photo(chat, 'photo.jpg', caption='Check this out!')
|
||||
"""
|
||||
return await send_photo(
|
||||
self,
|
||||
@@ -1178,6 +1296,9 @@ class Client:
|
||||
compress=compress,
|
||||
width=width,
|
||||
height=height,
|
||||
caption=caption,
|
||||
caption_markdown=caption_markdown,
|
||||
caption_html=caption_html,
|
||||
)
|
||||
|
||||
async def send_video(
|
||||
@@ -1194,6 +1315,9 @@ class Client:
|
||||
height: Optional[int] = None,
|
||||
round: bool = False,
|
||||
supports_streaming: bool = False,
|
||||
caption: Optional[str] = None,
|
||||
caption_markdown: Optional[str] = None,
|
||||
caption_html: Optional[str] = None,
|
||||
) -> Message:
|
||||
"""
|
||||
Send a video file.
|
||||
@@ -1208,6 +1332,12 @@ class Client:
|
||||
A local file path or :class:`~telethon.types.File` to send.
|
||||
|
||||
The rest of parameters behave the same as they do in :meth:`send_file`.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
await client.send_video(chat, 'video.mp4', caption_markdown='*I cannot believe this just happened*')
|
||||
"""
|
||||
return await send_video(
|
||||
self,
|
||||
@@ -1222,8 +1352,20 @@ class Client:
|
||||
height=height,
|
||||
round=round,
|
||||
supports_streaming=supports_streaming,
|
||||
caption=caption,
|
||||
caption_markdown=caption_markdown,
|
||||
caption_html=caption_html,
|
||||
)
|
||||
|
||||
def set_admin_rights(self, chat: ChatLike, user: ChatLike) -> None:
|
||||
set_admin_rights(self, chat, user)
|
||||
|
||||
def set_banned_rights(self, chat: ChatLike, user: ChatLike) -> None:
|
||||
set_banned_rights(self, chat, user)
|
||||
|
||||
def set_default_rights(self, chat: ChatLike, user: ChatLike) -> None:
|
||||
set_default_rights(self, chat, user)
|
||||
|
||||
def set_handler_filter(
|
||||
self,
|
||||
handler: Callable[[Event], Awaitable[Any]],
|
||||
@@ -1314,7 +1456,7 @@ class Client:
|
||||
"""
|
||||
await unpin_message(self, chat, message_id)
|
||||
|
||||
# ---
|
||||
# End partially @generated
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
|
@@ -2,8 +2,8 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...tl import abcs, functions, types
|
||||
from ..types import AsyncList, ChatLike, Dialog, User
|
||||
from ...tl import functions, types
|
||||
from ..types import AsyncList, ChatLike, Dialog, Draft
|
||||
from ..utils import build_chat_map
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -78,3 +78,29 @@ async def delete_dialog(self: Client, chat: ChatLike) -> None:
|
||||
max_date=None,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class DraftList(AsyncList[Draft]):
|
||||
def __init__(self, client: Client):
|
||||
super().__init__()
|
||||
self._client = client
|
||||
self._offset = 0
|
||||
|
||||
async def _fetch_next(self) -> None:
|
||||
result = await self._client(functions.messages.get_all_drafts())
|
||||
assert isinstance(result, types.Updates)
|
||||
|
||||
chat_map = build_chat_map(result.users, result.chats)
|
||||
|
||||
self._buffer.extend(
|
||||
Draft._from_raw(u, chat_map)
|
||||
for u in result.updates
|
||||
if isinstance(u, types.UpdateDraftMessage)
|
||||
)
|
||||
|
||||
self._total = len(result.updates)
|
||||
self._done = True
|
||||
|
||||
|
||||
def get_drafts(self: Client) -> AsyncList[Draft]:
|
||||
return DraftList(self)
|
||||
|
@@ -1,12 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
from functools import partial
|
||||
from inspect import isawaitable
|
||||
from io import BufferedWriter
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Coroutine, Optional, Tuple, Union
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from ...tl import abcs, functions, types
|
||||
from ..types import (
|
||||
@@ -43,6 +40,9 @@ async def send_photo(
|
||||
compress: bool = True,
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
caption: Optional[str] = None,
|
||||
caption_markdown: Optional[str] = None,
|
||||
caption_html: Optional[str] = None,
|
||||
) -> Message:
|
||||
return await send_file(
|
||||
self,
|
||||
@@ -55,6 +55,9 @@ async def send_photo(
|
||||
compress=compress,
|
||||
width=width,
|
||||
height=height,
|
||||
caption=caption,
|
||||
caption_markdown=caption_markdown,
|
||||
caption_html=caption_html,
|
||||
)
|
||||
|
||||
|
||||
@@ -71,6 +74,9 @@ async def send_audio(
|
||||
voice: bool = False,
|
||||
title: Optional[str] = None,
|
||||
performer: Optional[str] = None,
|
||||
caption: Optional[str] = None,
|
||||
caption_markdown: Optional[str] = None,
|
||||
caption_html: Optional[str] = None,
|
||||
) -> Message:
|
||||
return await send_file(
|
||||
self,
|
||||
@@ -84,6 +90,9 @@ async def send_audio(
|
||||
voice=voice,
|
||||
title=title,
|
||||
performer=performer,
|
||||
caption=caption,
|
||||
caption_markdown=caption_markdown,
|
||||
caption_html=caption_html,
|
||||
)
|
||||
|
||||
|
||||
@@ -101,6 +110,9 @@ async def send_video(
|
||||
height: Optional[int] = None,
|
||||
round: bool = False,
|
||||
supports_streaming: bool = False,
|
||||
caption: Optional[str] = None,
|
||||
caption_markdown: Optional[str] = None,
|
||||
caption_html: Optional[str] = None,
|
||||
) -> Message:
|
||||
return await send_file(
|
||||
self,
|
||||
@@ -115,6 +127,9 @@ async def send_video(
|
||||
height=height,
|
||||
round=round,
|
||||
supports_streaming=supports_streaming,
|
||||
caption=caption,
|
||||
caption_markdown=caption_markdown,
|
||||
caption_html=caption_html,
|
||||
)
|
||||
|
||||
|
||||
|
@@ -319,6 +319,7 @@ class SearchList(MessageList):
|
||||
self._peer: Optional[abcs.InputPeer] = None
|
||||
self._limit = limit
|
||||
self._query = query
|
||||
self._filter = types.InputMessagesFilterEmpty()
|
||||
self._offset_id = offset_id
|
||||
self._offset_date = offset_date
|
||||
|
||||
@@ -334,7 +335,7 @@ class SearchList(MessageList):
|
||||
q=self._query,
|
||||
from_id=None,
|
||||
top_msg_id=None,
|
||||
filter=types.InputMessagesFilterEmpty(),
|
||||
filter=self._filter,
|
||||
min_date=0,
|
||||
max_date=self._offset_date,
|
||||
offset_id=self._offset_id,
|
||||
|
@@ -7,12 +7,10 @@ from typing import (
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from ...session import Gap
|
||||
|
@@ -1,12 +1,14 @@
|
||||
from .async_list import AsyncList
|
||||
from .chat import Channel, Chat, ChatLike, Group, RestrictionReason, User
|
||||
from .dialog import Dialog
|
||||
from .draft import Draft
|
||||
from .file import File, InFileLike, InWrapper, OutFileLike, OutWrapper
|
||||
from .login_token import LoginToken
|
||||
from .message import Message
|
||||
from .meta import NoPublicConstructor
|
||||
from .participant import Participant
|
||||
from .password_token import PasswordToken
|
||||
from .recent_action import RecentAction
|
||||
|
||||
__all__ = [
|
||||
"AsyncList",
|
||||
@@ -17,6 +19,7 @@ __all__ = [
|
||||
"RestrictionReason",
|
||||
"User",
|
||||
"Dialog",
|
||||
"Draft",
|
||||
"File",
|
||||
"InFileLike",
|
||||
"InWrapper",
|
||||
@@ -27,4 +30,5 @@ __all__ = [
|
||||
"NoPublicConstructor",
|
||||
"Participant",
|
||||
"PasswordToken",
|
||||
"RecentAction",
|
||||
]
|
||||
|
29
client/src/telethon/_impl/client/types/draft.py
Normal file
29
client/src/telethon/_impl/client/types/draft.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from typing import Dict, List, Optional, Self
|
||||
|
||||
from ...session import PackedChat, PackedType
|
||||
from ...tl import abcs, types
|
||||
from .chat import Chat
|
||||
from .meta import NoPublicConstructor
|
||||
|
||||
|
||||
class Draft(metaclass=NoPublicConstructor):
|
||||
"""
|
||||
A draft message in a chat.
|
||||
"""
|
||||
|
||||
__slots__ = ("_raw", "_chat_map")
|
||||
|
||||
def __init__(
|
||||
self, raw: types.UpdateDraftMessage, chat_map: Dict[int, Chat]
|
||||
) -> None:
|
||||
self._raw = raw
|
||||
self._chat_map = chat_map
|
||||
|
||||
@classmethod
|
||||
def _from_raw(
|
||||
cls, draft: types.UpdateDraftMessage, chat_map: Dict[int, Chat]
|
||||
) -> Self:
|
||||
return cls._create(draft, chat_map)
|
||||
|
||||
async def delete(self) -> None:
|
||||
pass
|
@@ -137,7 +137,7 @@ class File(metaclass=NoPublicConstructor):
|
||||
self._raw = raw
|
||||
|
||||
@classmethod
|
||||
def _try_from_raw(cls, raw: abcs.MessageMedia) -> Optional[Self]:
|
||||
def _try_from_raw_message_media(cls, raw: abcs.MessageMedia) -> Optional[Self]:
|
||||
if isinstance(raw, types.MessageMediaDocument):
|
||||
if isinstance(raw.document, types.Document):
|
||||
return cls._create(
|
||||
@@ -176,30 +176,47 @@ class File(metaclass=NoPublicConstructor):
|
||||
raw=raw,
|
||||
)
|
||||
elif isinstance(raw, types.MessageMediaPhoto):
|
||||
if isinstance(raw.photo, types.Photo):
|
||||
return cls._create(
|
||||
path=None,
|
||||
file=None,
|
||||
attributes=[],
|
||||
size=max(map(photo_size_byte_count, raw.photo.sizes)),
|
||||
name="",
|
||||
mime="image/jpeg",
|
||||
photo=True,
|
||||
muted=False,
|
||||
input_media=types.InputMediaPhoto(
|
||||
spoiler=raw.spoiler,
|
||||
id=types.InputPhoto(
|
||||
id=raw.photo.id,
|
||||
access_hash=raw.photo.access_hash,
|
||||
file_reference=raw.photo.file_reference,
|
||||
),
|
||||
ttl_seconds=raw.ttl_seconds,
|
||||
),
|
||||
raw=raw,
|
||||
if raw.photo:
|
||||
return cls._try_from_raw_photo(
|
||||
raw.photo, spoiler=raw.spoiler, ttl_seconds=raw.ttl_seconds
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _try_from_raw_photo(
|
||||
cls,
|
||||
raw: abcs.Photo,
|
||||
*,
|
||||
spoiler: bool = False,
|
||||
ttl_seconds: Optional[int] = None,
|
||||
) -> Optional[Self]:
|
||||
if isinstance(raw, types.Photo):
|
||||
return cls._create(
|
||||
path=None,
|
||||
file=None,
|
||||
attributes=[],
|
||||
size=max(map(photo_size_byte_count, raw.sizes)),
|
||||
name="",
|
||||
mime="image/jpeg",
|
||||
photo=True,
|
||||
muted=False,
|
||||
input_media=types.InputMediaPhoto(
|
||||
spoiler=spoiler,
|
||||
id=types.InputPhoto(
|
||||
id=raw.id,
|
||||
access_hash=raw.access_hash,
|
||||
file_reference=raw.file_reference,
|
||||
),
|
||||
ttl_seconds=ttl_seconds,
|
||||
),
|
||||
raw=types.MessageMediaPhoto(
|
||||
spoiler=spoiler, photo=raw, ttl_seconds=ttl_seconds
|
||||
),
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def new(
|
||||
cls,
|
||||
|
@@ -39,8 +39,26 @@ class Message(metaclass=NoPublicConstructor):
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
"""
|
||||
The message identifier.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`/concepts/messages`, which contains an in-depth explanation of message counters.
|
||||
"""
|
||||
return self._raw.id
|
||||
|
||||
@property
|
||||
def grouped_id(self) -> Optional[int]:
|
||||
"""
|
||||
If the message is grouped with others in an album, return the group identifier.
|
||||
|
||||
Messages with the same :attr:`grouped_id` will belong to the same album.
|
||||
|
||||
Note that there can be messages in-between that do not have a :attr:`grouped_id`.
|
||||
"""
|
||||
return getattr(self._raw, "grouped_id", None)
|
||||
|
||||
@property
|
||||
def text(self) -> Optional[str]:
|
||||
return getattr(self._raw, "message", None)
|
||||
@@ -91,7 +109,7 @@ class Message(metaclass=NoPublicConstructor):
|
||||
|
||||
def _file(self) -> Optional[File]:
|
||||
return (
|
||||
File._try_from_raw(self._raw.media)
|
||||
File._try_from_raw_message_media(self._raw.media)
|
||||
if isinstance(self._raw, types.Message) and self._raw.media
|
||||
else None
|
||||
)
|
||||
|
29
client/src/telethon/_impl/client/types/recent_action.py
Normal file
29
client/src/telethon/_impl/client/types/recent_action.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from typing import Dict, List, Optional, Self, Union
|
||||
|
||||
from ...session import PackedChat, PackedType
|
||||
from ...tl import abcs, types
|
||||
from .chat import Chat
|
||||
from .meta import NoPublicConstructor
|
||||
|
||||
|
||||
class RecentAction(metaclass=NoPublicConstructor):
|
||||
"""
|
||||
A recent action in a chat, also known as an "admin log event action" or :tl:`ChannelAdminLogEvent`.
|
||||
|
||||
Only administrators of the chat can access these.
|
||||
"""
|
||||
|
||||
__slots__ = ("_raw", "_chat_map")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
event: abcs.ChannelAdminLogEvent,
|
||||
chat_map: Dict[int, Chat],
|
||||
) -> None:
|
||||
assert isinstance(event, types.ChannelAdminLogEvent)
|
||||
self._raw = event
|
||||
self._chat_map = chat_map
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
return self._raw.id
|
@@ -1,11 +1,11 @@
|
||||
import struct
|
||||
from enum import Enum
|
||||
from enum import IntFlag
|
||||
from typing import Optional, Self
|
||||
|
||||
from ...tl import abcs, types
|
||||
|
||||
|
||||
class PackedType(Enum):
|
||||
class PackedType(IntFlag):
|
||||
"""
|
||||
The type of a :class:`PackedChat`.
|
||||
"""
|
||||
@@ -52,6 +52,27 @@ class PackedChat:
|
||||
ty = PackedType(ty_byte & 0b0011_1111)
|
||||
return cls(ty, id, access_hash if has_hash else None)
|
||||
|
||||
@property
|
||||
def hex(self) -> str:
|
||||
"""
|
||||
Convenience property to convert to bytes and represent them as hexadecimal numbers:
|
||||
|
||||
.. code-block::
|
||||
|
||||
assert packed.hex == bytes(packed).hex()
|
||||
"""
|
||||
return bytes(self).hex()
|
||||
|
||||
def from_hex(cls, hex: str) -> Self:
|
||||
"""
|
||||
Convenience method to convert hexadecimal numbers into bytes then passed to :meth:`from_bytes`:
|
||||
|
||||
.. code-block::
|
||||
|
||||
assert PackedChat.from_hex(packed.hex) == packed
|
||||
"""
|
||||
return cls.from_bytes(bytes.fromhex(hex))
|
||||
|
||||
def is_user(self) -> bool:
|
||||
return self.ty in (PackedType.USER, PackedType.BOT)
|
||||
|
||||
@@ -93,13 +114,13 @@ class PackedChat:
|
||||
if self.is_user():
|
||||
return types.InputUser(user_id=self.id, access_hash=self.access_hash or 0)
|
||||
else:
|
||||
raise ValueError("chat is not user")
|
||||
raise TypeError("chat is not a user")
|
||||
|
||||
def _to_chat_id(self) -> int:
|
||||
if self.is_chat():
|
||||
return self.id
|
||||
else:
|
||||
raise ValueError("chat is not small group")
|
||||
raise TypeError("chat is not a group")
|
||||
|
||||
def _to_input_channel(self) -> types.InputChannel:
|
||||
if self.is_channel():
|
||||
@@ -107,7 +128,7 @@ class PackedChat:
|
||||
channel_id=self.id, access_hash=self.access_hash or 0
|
||||
)
|
||||
else:
|
||||
raise ValueError("chat is not channel")
|
||||
raise TypeError("chat is not a channel")
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, self.__class__):
|
||||
|
Reference in New Issue
Block a user