mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-06-02 20:53:09 +03:00
Work towards dialogs and drafts
This commit is contained in:
parent
864d5cd444
commit
b8b9836cf7
|
@ -73,6 +73,29 @@ To send a message with formatted text, use the ``markdown`` or ``html`` paramete
|
||||||
When sending files, the format is appended to the name of the ``caption`` parameter, either ``caption_markdown`` or ``caption_html``.
|
When sending files, the format is appended to the name of the ``caption`` parameter, either ``caption_markdown`` or ``caption_html``.
|
||||||
|
|
||||||
|
|
||||||
|
Link previews
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Link previews are treated as a type of media automatically generated by Telegram.
|
||||||
|
This means you cannot have both a link preview and other media in the same message.
|
||||||
|
|
||||||
|
The ``link_preview`` parameter indicates whether link previews are *allowed* to be present.
|
||||||
|
If Telegram is unable to generate a link preview for any of the links, there won't be a link preview.
|
||||||
|
|
||||||
|
By default, link previews are not enabled.
|
||||||
|
This is done to prevent sending things you did not explicitly intend to send.
|
||||||
|
Unlike the official clients, which do not have a GUI to "enable" the preview, you can easily enable them from code.
|
||||||
|
|
||||||
|
Telegram will attempt to generate a preview for all links contained in the message in order.
|
||||||
|
You can use this to your advantage, and hide a link to a photo in the first space or invisible character like ``'\u2063'``.
|
||||||
|
Note that avid users *will* be able to find out the link. It is not secret!
|
||||||
|
|
||||||
|
Link previews of photos won't show under the photos of the chat,
|
||||||
|
but it requires a server hosting the image, a public address, and Telegram to be able to generate a preview.
|
||||||
|
|
||||||
|
To regenerate a preview, send the corresponding link to `@WebpageBot <https://t.me/WebpageBot>`_.
|
||||||
|
|
||||||
|
|
||||||
Message identifiers
|
Message identifiers
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ from .chats import (
|
||||||
set_banned_rights,
|
set_banned_rights,
|
||||||
set_default_rights,
|
set_default_rights,
|
||||||
)
|
)
|
||||||
from .dialogs import delete_dialog, get_dialogs, get_drafts
|
from .dialogs import delete_dialog, edit_draft, get_dialogs, get_drafts
|
||||||
from .files import (
|
from .files import (
|
||||||
download,
|
download,
|
||||||
get_file_bytes,
|
get_file_bytes,
|
||||||
|
@ -499,7 +499,7 @@ class Client:
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
markdown: Optional[str] = None,
|
markdown: Optional[str] = None,
|
||||||
html: Optional[str] = None,
|
html: Optional[str] = None,
|
||||||
link_preview: Optional[bool] = None,
|
link_preview: bool = False,
|
||||||
) -> Message:
|
) -> Message:
|
||||||
"""
|
"""
|
||||||
Edit a message.
|
Edit a message.
|
||||||
|
@ -1247,6 +1247,7 @@ class Client:
|
||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
performer: Optional[str] = None,
|
performer: Optional[str] = None,
|
||||||
emoji: Optional[str] = None,
|
emoji: Optional[str] = None,
|
||||||
|
emoji_sticker: Optional[str] = None,
|
||||||
width: Optional[int] = None,
|
width: Optional[int] = None,
|
||||||
height: Optional[int] = None,
|
height: Optional[int] = None,
|
||||||
round: bool = False,
|
round: bool = False,
|
||||||
|
@ -1396,6 +1397,7 @@ class Client:
|
||||||
title=title,
|
title=title,
|
||||||
performer=performer,
|
performer=performer,
|
||||||
emoji=emoji,
|
emoji=emoji,
|
||||||
|
emoji_sticker=emoji_sticker,
|
||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
round=round,
|
round=round,
|
||||||
|
@ -1425,14 +1427,7 @@ class Client:
|
||||||
:param text: See :ref:`formatting`.
|
:param text: See :ref:`formatting`.
|
||||||
:param markdown: See :ref:`formatting`.
|
:param markdown: See :ref:`formatting`.
|
||||||
:param html: See :ref:`formatting`.
|
:param html: See :ref:`formatting`.
|
||||||
|
:param link_preview: See :ref:`formatting`.
|
||||||
:param link_preview:
|
|
||||||
Whether the link preview is allowed.
|
|
||||||
|
|
||||||
Setting this to :data:`True` does not guarantee a preview.
|
|
||||||
Telegram must be able to generate a preview from the first link in the message text.
|
|
||||||
|
|
||||||
To regenerate the preview, send the link to `@WebpageBot <https://t.me/WebpageBot>`_.
|
|
||||||
|
|
||||||
:param reply_to:
|
:param reply_to:
|
||||||
The message identifier of the message to reply to.
|
The message identifier of the message to reply to.
|
||||||
|
@ -1578,6 +1573,58 @@ class Client:
|
||||||
def set_default_rights(self, chat: ChatLike, user: ChatLike) -> None:
|
def set_default_rights(self, chat: ChatLike, user: ChatLike) -> None:
|
||||||
set_default_rights(self, chat, user)
|
set_default_rights(self, chat, user)
|
||||||
|
|
||||||
|
async def edit_draft(
|
||||||
|
self,
|
||||||
|
chat: ChatLike,
|
||||||
|
text: Optional[str] = None,
|
||||||
|
*,
|
||||||
|
markdown: Optional[str] = None,
|
||||||
|
html: Optional[str] = None,
|
||||||
|
link_preview: bool = False,
|
||||||
|
reply_to: Optional[int] = None,
|
||||||
|
) -> Draft:
|
||||||
|
"""
|
||||||
|
Set a draft message in a chat.
|
||||||
|
|
||||||
|
This can also be used to clear the draft by setting the text to an empty string ``""``.
|
||||||
|
|
||||||
|
:param chat:
|
||||||
|
The :term:`chat` where the draft will be saved to.
|
||||||
|
|
||||||
|
:param text: See :ref:`formatting`.
|
||||||
|
:param markdown: See :ref:`formatting`.
|
||||||
|
:param html: See :ref:`formatting`.
|
||||||
|
:param link_preview: See :ref:`formatting`.
|
||||||
|
|
||||||
|
:param reply_to:
|
||||||
|
The message identifier of the message to reply to.
|
||||||
|
|
||||||
|
:return: The created draft.
|
||||||
|
|
||||||
|
.. rubric:: Example
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Edit message to have text without formatting
|
||||||
|
await client.edit_message(chat, msg_id, text='New text')
|
||||||
|
|
||||||
|
# Remove the link preview without changing the text
|
||||||
|
await client.edit_message(chat, msg_id, link_preview=False)
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:meth:`telethon.types.Message.edit`
|
||||||
|
"""
|
||||||
|
return await edit_draft(
|
||||||
|
self,
|
||||||
|
chat,
|
||||||
|
text,
|
||||||
|
markdown=markdown,
|
||||||
|
html=html,
|
||||||
|
link_preview=link_preview,
|
||||||
|
reply_to=reply_to,
|
||||||
|
)
|
||||||
|
|
||||||
def set_handler_filter(
|
def set_handler_filter(
|
||||||
self,
|
self,
|
||||||
handler: Callable[[Event], Awaitable[Any]],
|
handler: Callable[[Event], Awaitable[Any]],
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
import time
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from ...tl import functions, types
|
from ...tl import functions, types
|
||||||
from ..types import AsyncList, ChatLike, Dialog, Draft
|
from ..types import AsyncList, ChatLike, Dialog, Draft
|
||||||
from ..utils import build_chat_map
|
from ..utils import build_chat_map, build_msg_map
|
||||||
|
from .messages import parse_message
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .client import Client
|
from .client import Client
|
||||||
|
@ -40,8 +42,11 @@ class DialogList(AsyncList[Dialog]):
|
||||||
assert isinstance(result, (types.messages.Dialogs, types.messages.DialogsSlice))
|
assert isinstance(result, (types.messages.Dialogs, types.messages.DialogsSlice))
|
||||||
|
|
||||||
chat_map = build_chat_map(result.users, result.chats)
|
chat_map = build_chat_map(result.users, result.chats)
|
||||||
|
msg_map = build_msg_map(self._client, result.messages, chat_map)
|
||||||
|
|
||||||
self._buffer.extend(Dialog._from_raw(d, chat_map) for d in result.dialogs)
|
self._buffer.extend(
|
||||||
|
Dialog._from_raw(self._client, d, chat_map, msg_map) for d in result.dialogs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_dialogs(self: Client) -> AsyncList[Dialog]:
|
def get_dialogs(self: Client) -> AsyncList[Dialog]:
|
||||||
|
@ -93,7 +98,7 @@ class DraftList(AsyncList[Draft]):
|
||||||
chat_map = build_chat_map(result.users, result.chats)
|
chat_map = build_chat_map(result.users, result.chats)
|
||||||
|
|
||||||
self._buffer.extend(
|
self._buffer.extend(
|
||||||
Draft._from_raw(u, chat_map)
|
Draft._from_raw_update(self._client, u, chat_map)
|
||||||
for u in result.updates
|
for u in result.updates
|
||||||
if isinstance(u, types.UpdateDraftMessage)
|
if isinstance(u, types.UpdateDraftMessage)
|
||||||
)
|
)
|
||||||
|
@ -104,3 +109,47 @@ class DraftList(AsyncList[Draft]):
|
||||||
|
|
||||||
def get_drafts(self: Client) -> AsyncList[Draft]:
|
def get_drafts(self: Client) -> AsyncList[Draft]:
|
||||||
return DraftList(self)
|
return DraftList(self)
|
||||||
|
|
||||||
|
|
||||||
|
async def edit_draft(
|
||||||
|
self: Client,
|
||||||
|
chat: ChatLike,
|
||||||
|
text: Optional[str] = None,
|
||||||
|
*,
|
||||||
|
markdown: Optional[str] = None,
|
||||||
|
html: Optional[str] = None,
|
||||||
|
link_preview: bool = False,
|
||||||
|
reply_to: Optional[int] = None,
|
||||||
|
) -> Draft:
|
||||||
|
packed = await self._resolve_to_packed(chat)
|
||||||
|
peer = (await self._resolve_to_packed(chat))._to_input_peer()
|
||||||
|
message, entities = parse_message(
|
||||||
|
text=text, markdown=markdown, html=html, allow_empty=False
|
||||||
|
)
|
||||||
|
assert isinstance(message, str)
|
||||||
|
|
||||||
|
result = await self(
|
||||||
|
functions.messages.save_draft(
|
||||||
|
no_webpage=not link_preview,
|
||||||
|
reply_to_msg_id=reply_to,
|
||||||
|
top_msg_id=None,
|
||||||
|
peer=peer,
|
||||||
|
message=message,
|
||||||
|
entities=entities,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert result
|
||||||
|
|
||||||
|
return Draft._from_raw(
|
||||||
|
client=self,
|
||||||
|
peer=packed._to_peer(),
|
||||||
|
top_msg_id=0,
|
||||||
|
draft=types.DraftMessage(
|
||||||
|
no_webpage=not link_preview,
|
||||||
|
reply_to_msg_id=reply_to,
|
||||||
|
message=message,
|
||||||
|
entities=entities,
|
||||||
|
date=int(time.time()),
|
||||||
|
),
|
||||||
|
chat_map={},
|
||||||
|
)
|
||||||
|
|
|
@ -46,7 +46,7 @@ async def send_message(
|
||||||
*,
|
*,
|
||||||
markdown: Optional[str] = None,
|
markdown: Optional[str] = None,
|
||||||
html: Optional[str] = None,
|
html: Optional[str] = None,
|
||||||
link_preview: Optional[bool] = None,
|
link_preview: bool = False,
|
||||||
reply_to: Optional[int] = None,
|
reply_to: Optional[int] = None,
|
||||||
) -> Message:
|
) -> Message:
|
||||||
packed = await self._resolve_to_packed(chat)
|
packed = await self._resolve_to_packed(chat)
|
||||||
|
@ -99,51 +99,29 @@ async def send_message(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if isinstance(result, types.UpdateShortSentMessage):
|
if isinstance(result, types.UpdateShortSentMessage):
|
||||||
return Message._from_raw(
|
return Message._from_defaults(
|
||||||
self,
|
self,
|
||||||
types.Message(
|
|
||||||
out=result.out,
|
|
||||||
mentioned=False,
|
|
||||||
media_unread=False,
|
|
||||||
silent=False,
|
|
||||||
post=False,
|
|
||||||
from_scheduled=False,
|
|
||||||
legacy=False,
|
|
||||||
edit_hide=False,
|
|
||||||
pinned=False,
|
|
||||||
noforwards=False,
|
|
||||||
id=result.id,
|
|
||||||
from_id=types.PeerUser(user_id=self._session.user.id)
|
|
||||||
if self._session.user
|
|
||||||
else None,
|
|
||||||
peer_id=packed._to_peer(),
|
|
||||||
fwd_from=None,
|
|
||||||
via_bot_id=None,
|
|
||||||
reply_to=types.MessageReplyHeader(
|
|
||||||
reply_to_scheduled=False,
|
|
||||||
forum_topic=False,
|
|
||||||
reply_to_msg_id=reply_to,
|
|
||||||
reply_to_peer_id=None,
|
|
||||||
reply_to_top_id=None,
|
|
||||||
)
|
|
||||||
if reply_to
|
|
||||||
else None,
|
|
||||||
date=result.date,
|
|
||||||
message=message if isinstance(message, str) else (message.text or ""),
|
|
||||||
media=result.media,
|
|
||||||
reply_markup=None,
|
|
||||||
entities=result.entities,
|
|
||||||
views=None,
|
|
||||||
forwards=None,
|
|
||||||
replies=None,
|
|
||||||
edit_date=None,
|
|
||||||
post_author=None,
|
|
||||||
grouped_id=None,
|
|
||||||
reactions=None,
|
|
||||||
restriction_reason=None,
|
|
||||||
ttl_period=result.ttl_period,
|
|
||||||
),
|
|
||||||
{},
|
{},
|
||||||
|
out=result.out,
|
||||||
|
id=result.id,
|
||||||
|
from_id=types.PeerUser(user_id=self._session.user.id)
|
||||||
|
if self._session.user
|
||||||
|
else None,
|
||||||
|
peer_id=packed._to_peer(),
|
||||||
|
reply_to=types.MessageReplyHeader(
|
||||||
|
reply_to_scheduled=False,
|
||||||
|
forum_topic=False,
|
||||||
|
reply_to_msg_id=reply_to,
|
||||||
|
reply_to_peer_id=None,
|
||||||
|
reply_to_top_id=None,
|
||||||
|
)
|
||||||
|
if reply_to
|
||||||
|
else None,
|
||||||
|
date=result.date,
|
||||||
|
message=message if isinstance(message, str) else (message.text or ""),
|
||||||
|
media=result.media,
|
||||||
|
entities=result.entities,
|
||||||
|
ttl_period=result.ttl_period,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return self._build_message_map(result, peer).with_random_id(random_id)
|
return self._build_message_map(result, peer).with_random_id(random_id)
|
||||||
|
@ -157,7 +135,7 @@ async def edit_message(
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
markdown: Optional[str] = None,
|
markdown: Optional[str] = None,
|
||||||
html: Optional[str] = None,
|
html: Optional[str] = None,
|
||||||
link_preview: Optional[bool] = None,
|
link_preview: bool = False,
|
||||||
) -> Message:
|
) -> Message:
|
||||||
peer = (await self._resolve_to_packed(chat))._to_input_peer()
|
peer = (await self._resolve_to_packed(chat))._to_input_peer()
|
||||||
message, entities = parse_message(
|
message, entities = parse_message(
|
||||||
|
|
|
@ -52,7 +52,7 @@ class Group(Chat, metaclass=NoPublicConstructor):
|
||||||
|
|
||||||
This property is always present, but may be the empty string.
|
This property is always present, but may be the empty string.
|
||||||
"""
|
"""
|
||||||
return self._raw.title
|
return getattr(self._raw, "title", None) or ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def username(self) -> Optional[str]:
|
def username(self) -> Optional[str]:
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
from typing import Dict, Self
|
from __future__ import annotations
|
||||||
|
|
||||||
from ...tl import abcs
|
from typing import TYPE_CHECKING, Dict, Optional, Self, Union
|
||||||
|
|
||||||
|
from ...tl import abcs, types
|
||||||
|
from ..utils import peer_id
|
||||||
from .chat import Chat
|
from .chat import Chat
|
||||||
|
from .draft import Draft
|
||||||
|
from .message import Message
|
||||||
from .meta import NoPublicConstructor
|
from .meta import NoPublicConstructor
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..client import Client
|
||||||
|
|
||||||
|
|
||||||
class Dialog(metaclass=NoPublicConstructor):
|
class Dialog(metaclass=NoPublicConstructor):
|
||||||
"""
|
"""
|
||||||
|
@ -16,12 +24,76 @@ class Dialog(metaclass=NoPublicConstructor):
|
||||||
You can obtain dialogs with methods such as :meth:`telethon.Client.get_dialogs`.
|
You can obtain dialogs with methods such as :meth:`telethon.Client.get_dialogs`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("_raw", "_chat_map")
|
def __init__(
|
||||||
|
self,
|
||||||
def __init__(self, raw: abcs.Dialog, chat_map: Dict[int, Chat]) -> None:
|
client: Client,
|
||||||
|
raw: Union[types.Dialog, types.DialogFolder],
|
||||||
|
chat_map: Dict[int, Chat],
|
||||||
|
msg_map: Dict[int, Message],
|
||||||
|
) -> None:
|
||||||
|
self._client = client
|
||||||
self._raw = raw
|
self._raw = raw
|
||||||
self._chat_map = chat_map
|
self._chat_map = chat_map
|
||||||
|
self._msg_map = msg_map
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_raw(cls, dialog: abcs.Dialog, chat_map: Dict[int, Chat]) -> Self:
|
def _from_raw(
|
||||||
return cls._create(dialog, chat_map)
|
cls,
|
||||||
|
client: Client,
|
||||||
|
dialog: abcs.Dialog,
|
||||||
|
chat_map: Dict[int, Chat],
|
||||||
|
msg_map: Dict[int, Message],
|
||||||
|
) -> Self:
|
||||||
|
assert isinstance(dialog, (types.Dialog, types.DialogFolder))
|
||||||
|
return cls._create(client, dialog, chat_map, msg_map)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def chat(self) -> Chat:
|
||||||
|
"""
|
||||||
|
The chat where messages are sent in this dialog.
|
||||||
|
"""
|
||||||
|
return self._chat_map[peer_id(self._raw.peer)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def draft(self) -> Optional[Draft]:
|
||||||
|
"""
|
||||||
|
The message draft within this dialog, if any.
|
||||||
|
|
||||||
|
This property does not update when the draft changes.
|
||||||
|
"""
|
||||||
|
if isinstance(self._raw, types.Dialog) and self._raw.draft:
|
||||||
|
return Draft._from_raw(
|
||||||
|
self._client,
|
||||||
|
self._raw.peer,
|
||||||
|
self._raw.top_message,
|
||||||
|
self._raw.draft,
|
||||||
|
self._chat_map,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def latest_message(self) -> Optional[Message]:
|
||||||
|
"""
|
||||||
|
The latest message sent or received in this dialog, if any.
|
||||||
|
|
||||||
|
This property does not update when new messages arrive.
|
||||||
|
"""
|
||||||
|
return self._msg_map.get(self._raw.top_message)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unread_count(self) -> int:
|
||||||
|
"""
|
||||||
|
The amount of unread messages in this dialog.
|
||||||
|
|
||||||
|
This property does not update when messages are read or sent.
|
||||||
|
"""
|
||||||
|
if isinstance(self._raw, types.Dialog):
|
||||||
|
return self._raw.unread_count
|
||||||
|
elif isinstance(self._raw, types.DialogPeerFolder):
|
||||||
|
return (
|
||||||
|
self._raw.unread_unmuted_messages_count
|
||||||
|
+ self._raw.unread_muted_messages_count
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
from typing import Dict, Self
|
from __future__ import annotations
|
||||||
|
|
||||||
from ...tl import types
|
import datetime
|
||||||
|
from typing import TYPE_CHECKING, Dict, Optional, Self
|
||||||
|
|
||||||
|
from ...session import PackedChat
|
||||||
|
from ...tl import abcs, functions, types
|
||||||
|
from ..parsers import generate_html_message, generate_markdown_message
|
||||||
|
from ..utils import expand_peer, generate_random_id, peer_id
|
||||||
from .chat import Chat
|
from .chat import Chat
|
||||||
|
from .message import Message
|
||||||
from .meta import NoPublicConstructor
|
from .meta import NoPublicConstructor
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..client import Client
|
||||||
|
|
||||||
|
|
||||||
class Draft(metaclass=NoPublicConstructor):
|
class Draft(metaclass=NoPublicConstructor):
|
||||||
"""
|
"""
|
||||||
|
@ -12,19 +22,226 @@ class Draft(metaclass=NoPublicConstructor):
|
||||||
You can obtain drafts with methods such as :meth:`telethon.Client.get_drafts`.
|
You can obtain drafts with methods such as :meth:`telethon.Client.get_drafts`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("_raw", "_chat_map")
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, raw: types.UpdateDraftMessage, chat_map: Dict[int, Chat]
|
self,
|
||||||
|
client: Client,
|
||||||
|
peer: abcs.Peer,
|
||||||
|
top_msg_id: Optional[int],
|
||||||
|
raw: abcs.DraftMessage,
|
||||||
|
chat_map: Dict[int, Chat],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
assert isinstance(raw, (types.DraftMessage, types.DraftMessageEmpty))
|
||||||
|
self._client = client
|
||||||
|
self._peer = peer
|
||||||
self._raw = raw
|
self._raw = raw
|
||||||
|
self._top_msg_id = top_msg_id
|
||||||
self._chat_map = chat_map
|
self._chat_map = chat_map
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_raw(
|
def _from_raw_update(
|
||||||
cls, draft: types.UpdateDraftMessage, chat_map: Dict[int, Chat]
|
cls, client: Client, draft: types.UpdateDraftMessage, chat_map: Dict[int, Chat]
|
||||||
) -> Self:
|
) -> Self:
|
||||||
return cls._create(draft, chat_map)
|
return cls._create(client, draft.peer, draft.top_msg_id, draft.draft, chat_map)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _from_raw(
|
||||||
|
cls,
|
||||||
|
client: Client,
|
||||||
|
peer: abcs.Peer,
|
||||||
|
top_msg_id: int,
|
||||||
|
draft: abcs.DraftMessage,
|
||||||
|
chat_map: Dict[int, Chat],
|
||||||
|
) -> Self:
|
||||||
|
return cls._create(client, peer, top_msg_id, draft, chat_map)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def chat(self) -> Chat:
|
||||||
|
"""
|
||||||
|
The chat where the draft will be sent to.
|
||||||
|
"""
|
||||||
|
return self._chat_map.get(peer_id(self._peer)) or expand_peer(
|
||||||
|
self._peer, broadcast=None
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def link_preview(self) -> bool:
|
||||||
|
"""
|
||||||
|
:data:`True` if the link preview is allowed to exist when sending the message.
|
||||||
|
"""
|
||||||
|
return not getattr(self._raw, "no_webpage", False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def replied_message_id(self) -> Optional[int]:
|
||||||
|
"""
|
||||||
|
Get the message identifier of message this draft will reply to once sent.
|
||||||
|
"""
|
||||||
|
return getattr(self._raw, "reply_to_msg_id") or None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
The :attr:`~Message.text` of the message that will be sent.
|
||||||
|
"""
|
||||||
|
return getattr(self._raw, "message", None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text_html(self) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
The :attr:`~Message.text_html` of the message that will be sent.
|
||||||
|
"""
|
||||||
|
if text := getattr(self._raw, "message", None):
|
||||||
|
return generate_html_message(
|
||||||
|
text, getattr(self._raw, "entities", None) or []
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text_markdown(self) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
The :attr:`~Message.text_markdown` of the message that will be sent.
|
||||||
|
"""
|
||||||
|
if text := getattr(self._raw, "message", None):
|
||||||
|
return generate_markdown_message(
|
||||||
|
text, getattr(self._raw, "entities", None) or []
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def date(self) -> Optional[datetime.datetime]:
|
||||||
|
"""
|
||||||
|
The date when the draft was last updated.
|
||||||
|
"""
|
||||||
|
date = getattr(self._raw, "date", None)
|
||||||
|
return (
|
||||||
|
datetime.datetime.fromtimestamp(date, tz=datetime.timezone.utc)
|
||||||
|
if date is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
async def edit(
|
||||||
|
self,
|
||||||
|
text: Optional[str] = None,
|
||||||
|
*,
|
||||||
|
markdown: Optional[str] = None,
|
||||||
|
html: Optional[str] = None,
|
||||||
|
link_preview: bool = False,
|
||||||
|
reply_to: Optional[int] = None,
|
||||||
|
) -> Draft:
|
||||||
|
"""
|
||||||
|
Replace the current draft with a new one.
|
||||||
|
|
||||||
|
:param text: See :ref:`formatting`.
|
||||||
|
:param markdown: See :ref:`formatting`.
|
||||||
|
:param html: See :ref:`formatting`.
|
||||||
|
:param link_preview: See :ref:`formatting`.
|
||||||
|
|
||||||
|
:param reply_to:
|
||||||
|
The message identifier of the message to reply to.
|
||||||
|
|
||||||
|
:return: The edited draft.
|
||||||
|
|
||||||
|
.. rubric:: Example
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
new_draft = await old_draft.edit('new text', link_preview=False)
|
||||||
|
"""
|
||||||
|
return await self._client.edit_draft(
|
||||||
|
await self._packed_chat(),
|
||||||
|
text,
|
||||||
|
markdown=markdown,
|
||||||
|
html=html,
|
||||||
|
link_preview=link_preview,
|
||||||
|
reply_to=reply_to,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _packed_chat(self) -> PackedChat:
|
||||||
|
packed = None
|
||||||
|
if chat := self._chat_map.get(peer_id(self._peer)):
|
||||||
|
packed = chat.pack()
|
||||||
|
if packed is None:
|
||||||
|
packed = await self._client.resolve_to_packed(peer_id(self._peer))
|
||||||
|
return packed
|
||||||
|
|
||||||
|
async def send(self) -> Message:
|
||||||
|
"""
|
||||||
|
Send the contents of this draft to the chat.
|
||||||
|
|
||||||
|
The draft will be cleared after being sent.
|
||||||
|
|
||||||
|
:return: The sent message.
|
||||||
|
|
||||||
|
.. rubric:: Example
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
await draft.send(clear=False)
|
||||||
|
"""
|
||||||
|
|
||||||
|
packed = await self._packed_chat()
|
||||||
|
peer = packed._to_input_peer()
|
||||||
|
|
||||||
|
reply_to = self.replied_message_id
|
||||||
|
message = getattr(self._raw, "message", "")
|
||||||
|
entities = getattr(self._raw, "entities", None)
|
||||||
|
random_id = generate_random_id()
|
||||||
|
|
||||||
|
result = await self._client(
|
||||||
|
functions.messages.send_message(
|
||||||
|
no_webpage=not self.link_preview,
|
||||||
|
silent=False,
|
||||||
|
background=False,
|
||||||
|
clear_draft=True,
|
||||||
|
noforwards=False,
|
||||||
|
update_stickersets_order=False,
|
||||||
|
peer=peer,
|
||||||
|
reply_to=types.InputReplyToMessage(
|
||||||
|
reply_to_msg_id=reply_to, top_msg_id=None
|
||||||
|
)
|
||||||
|
if reply_to
|
||||||
|
else None,
|
||||||
|
message=message,
|
||||||
|
random_id=random_id,
|
||||||
|
reply_markup=None,
|
||||||
|
entities=entities,
|
||||||
|
schedule_date=None,
|
||||||
|
send_as=None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if isinstance(result, types.UpdateShortSentMessage):
|
||||||
|
return Message._from_defaults(
|
||||||
|
self._client,
|
||||||
|
{},
|
||||||
|
out=result.out,
|
||||||
|
id=result.id,
|
||||||
|
from_id=types.PeerUser(user_id=self._client._session.user.id)
|
||||||
|
if self._client._session.user
|
||||||
|
else None,
|
||||||
|
peer_id=packed._to_peer(),
|
||||||
|
reply_to=types.MessageReplyHeader(
|
||||||
|
reply_to_scheduled=False,
|
||||||
|
forum_topic=False,
|
||||||
|
reply_to_msg_id=reply_to,
|
||||||
|
reply_to_peer_id=None,
|
||||||
|
reply_to_top_id=None,
|
||||||
|
)
|
||||||
|
if reply_to
|
||||||
|
else None,
|
||||||
|
date=result.date,
|
||||||
|
message=message,
|
||||||
|
media=result.media,
|
||||||
|
entities=result.entities,
|
||||||
|
ttl_period=result.ttl_period,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return self._client._build_message_map(result, peer).with_random_id(
|
||||||
|
random_id
|
||||||
|
)
|
||||||
|
|
||||||
async def delete(self) -> None:
|
async def delete(self) -> None:
|
||||||
pass
|
"""
|
||||||
|
Clear the contents of this draft to delete it.
|
||||||
|
"""
|
||||||
|
await self.edit("")
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from typing import TYPE_CHECKING, Dict, Optional, Self, Union
|
from typing import TYPE_CHECKING, Any, Dict, Optional, Self, Union
|
||||||
|
|
||||||
from ...tl import abcs, types
|
from ...tl import abcs, types
|
||||||
from ..parsers import generate_html_message, generate_markdown_message
|
from ..parsers import generate_html_message, generate_markdown_message
|
||||||
from ..utils import expand_peer, peer_id
|
from ..utils import adapt_date, expand_peer, peer_id
|
||||||
from .chat import Chat, ChatLike
|
from .chat import Chat, ChatLike
|
||||||
from .file import File
|
from .file import File
|
||||||
from .meta import NoPublicConstructor
|
from .meta import NoPublicConstructor
|
||||||
|
@ -40,6 +40,52 @@ class Message(metaclass=NoPublicConstructor):
|
||||||
) -> Self:
|
) -> Self:
|
||||||
return cls._create(client, message, chat_map)
|
return cls._create(client, message, chat_map)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _from_defaults(
|
||||||
|
cls,
|
||||||
|
client: Client,
|
||||||
|
chat_map: Dict[int, Chat],
|
||||||
|
id: int,
|
||||||
|
peer_id: abcs.Peer,
|
||||||
|
date: int,
|
||||||
|
message: str,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> Self:
|
||||||
|
default_kwargs: Dict[str, Any] = {
|
||||||
|
"out": False,
|
||||||
|
"mentioned": False,
|
||||||
|
"media_unread": False,
|
||||||
|
"silent": False,
|
||||||
|
"post": False,
|
||||||
|
"from_scheduled": False,
|
||||||
|
"legacy": False,
|
||||||
|
"edit_hide": False,
|
||||||
|
"pinned": False,
|
||||||
|
"noforwards": False,
|
||||||
|
"id": id,
|
||||||
|
"from_id": None,
|
||||||
|
"peer_id": peer_id,
|
||||||
|
"fwd_from": None,
|
||||||
|
"via_bot_id": None,
|
||||||
|
"reply_to": None,
|
||||||
|
"date": date,
|
||||||
|
"message": message,
|
||||||
|
"media": None,
|
||||||
|
"reply_markup": None,
|
||||||
|
"entities": None,
|
||||||
|
"views": None,
|
||||||
|
"forwards": None,
|
||||||
|
"replies": None,
|
||||||
|
"edit_date": None,
|
||||||
|
"post_author": None,
|
||||||
|
"grouped_id": None,
|
||||||
|
"reactions": None,
|
||||||
|
"restriction_reason": None,
|
||||||
|
"ttl_period": None,
|
||||||
|
}
|
||||||
|
default_kwargs.update(kwargs)
|
||||||
|
return cls._create(client, types.Message(**default_kwargs), chat_map)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self) -> int:
|
def id(self) -> int:
|
||||||
"""
|
"""
|
||||||
|
@ -86,12 +132,7 @@ class Message(metaclass=NoPublicConstructor):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date(self) -> Optional[datetime.datetime]:
|
def date(self) -> Optional[datetime.datetime]:
|
||||||
date = getattr(self._raw, "date", None)
|
return adapt_date(getattr(self._raw, "date", None))
|
||||||
return (
|
|
||||||
datetime.datetime.fromtimestamp(date, tz=datetime.timezone.utc)
|
|
||||||
if date is not None
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def chat(self) -> Chat:
|
def chat(self) -> Chat:
|
||||||
|
@ -236,7 +277,7 @@ class Message(metaclass=NoPublicConstructor):
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
markdown: Optional[str] = None,
|
markdown: Optional[str] = None,
|
||||||
html: Optional[str] = None,
|
html: Optional[str] = None,
|
||||||
link_preview: Optional[bool] = None,
|
link_preview: bool = False,
|
||||||
) -> Message:
|
) -> Message:
|
||||||
"""
|
"""
|
||||||
Alias for :meth:`telethon.Client.edit_message`.
|
Alias for :meth:`telethon.Client.edit_message`.
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import datetime
|
||||||
import itertools
|
import itertools
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import DefaultDict, Dict, List, Optional, Union
|
from typing import TYPE_CHECKING, DefaultDict, Dict, List, Optional, Union
|
||||||
|
|
||||||
from ..tl import abcs, types
|
from ..tl import abcs, types
|
||||||
from .types import Channel, Chat, Group, User
|
from .types import Channel, Chat, Group, Message, User
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .client import Client
|
||||||
|
|
||||||
_last_id = 0
|
_last_id = 0
|
||||||
|
|
||||||
|
@ -51,6 +57,15 @@ def build_chat_map(users: List[abcs.User], chats: List[abcs.Chat]) -> Dict[int,
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def build_msg_map(
|
||||||
|
client: Client, messages: List[abcs.Message], chat_map: Dict[int, Chat]
|
||||||
|
) -> Dict[int, Message]:
|
||||||
|
return {
|
||||||
|
msg.id: msg
|
||||||
|
for msg in (Message._from_raw(client, m, chat_map) for m in messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def peer_id(peer: abcs.Peer) -> int:
|
def peer_id(peer: abcs.Peer) -> int:
|
||||||
if isinstance(peer, types.PeerUser):
|
if isinstance(peer, types.PeerUser):
|
||||||
return peer.user_id
|
return peer.user_id
|
||||||
|
@ -83,3 +98,11 @@ def expand_peer(peer: abcs.Peer, *, broadcast: Optional[bool]) -> Chat:
|
||||||
return Channel._from_raw(channel) if broadcast else Group._from_raw(channel)
|
return Channel._from_raw(channel) if broadcast else Group._from_raw(channel)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("unexpected case")
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
|
||||||
|
def adapt_date(date: Optional[int]) -> Optional[datetime.datetime]:
|
||||||
|
return (
|
||||||
|
datetime.datetime.fromtimestamp(date, tz=datetime.timezone.utc)
|
||||||
|
if date is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
|
@ -208,7 +208,7 @@ class Encrypted(Mtp):
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._msg_count == 1:
|
if self._msg_count == 1:
|
||||||
container_msg_id = Single
|
container_msg_id: Union[Type[Single], int] = Single
|
||||||
else:
|
else:
|
||||||
container_msg_id = self._get_new_msg_id()
|
container_msg_id = self._get_new_msg_id()
|
||||||
self._buffer[HEADER_LEN : HEADER_LEN + CONTAINER_HEADER_LEN] = struct.pack(
|
self._buffer[HEADER_LEN : HEADER_LEN + CONTAINER_HEADER_LEN] = struct.pack(
|
||||||
|
|
Loading…
Reference in New Issue
Block a user