refactor(telegram): msgspec schemas and parsing (#156)
This commit is contained in:
@@ -1,53 +1,37 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from .api_schemas import (
|
||||||
|
CallbackQuery,
|
||||||
import msgspec
|
CallbackQueryMessage,
|
||||||
|
Chat,
|
||||||
|
ChatMember,
|
||||||
|
Document,
|
||||||
|
File,
|
||||||
|
ForumTopic,
|
||||||
|
Message,
|
||||||
|
MessageReply,
|
||||||
|
PhotoSize,
|
||||||
|
Sticker,
|
||||||
|
Update,
|
||||||
|
User,
|
||||||
|
Video,
|
||||||
|
Voice,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"CallbackQuery",
|
||||||
|
"CallbackQueryMessage",
|
||||||
"Chat",
|
"Chat",
|
||||||
"ChatMember",
|
"ChatMember",
|
||||||
|
"Document",
|
||||||
"File",
|
"File",
|
||||||
"ForumTopic",
|
"ForumTopic",
|
||||||
"Message",
|
"Message",
|
||||||
|
"MessageReply",
|
||||||
|
"PhotoSize",
|
||||||
|
"Sticker",
|
||||||
"Update",
|
"Update",
|
||||||
"User",
|
"User",
|
||||||
|
"Video",
|
||||||
|
"Voice",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class User(msgspec.Struct, forbid_unknown_fields=False):
|
|
||||||
id: int
|
|
||||||
username: str | None = None
|
|
||||||
first_name: str | None = None
|
|
||||||
last_name: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class Chat(msgspec.Struct, forbid_unknown_fields=False):
|
|
||||||
id: int
|
|
||||||
type: str
|
|
||||||
is_forum: bool | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class ChatMember(msgspec.Struct, forbid_unknown_fields=False):
|
|
||||||
status: str
|
|
||||||
can_manage_topics: bool | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class Message(msgspec.Struct, forbid_unknown_fields=False):
|
|
||||||
message_id: int
|
|
||||||
message_thread_id: int | None = None
|
|
||||||
text: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class File(msgspec.Struct, forbid_unknown_fields=False):
|
|
||||||
file_path: str
|
|
||||||
|
|
||||||
|
|
||||||
class ForumTopic(msgspec.Struct, forbid_unknown_fields=False):
|
|
||||||
message_thread_id: int
|
|
||||||
|
|
||||||
|
|
||||||
class Update(msgspec.Struct, forbid_unknown_fields=False):
|
|
||||||
update_id: int
|
|
||||||
message: dict[str, Any] | None = None
|
|
||||||
callback_query: dict[str, Any] | None = None
|
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
"""Msgspec models for Telegram Bot API payloads (subset used by takopi).
|
||||||
|
|
||||||
|
Derived from telegram-api.html in the repository.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import msgspec
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"CallbackQuery",
|
||||||
|
"CallbackQueryMessage",
|
||||||
|
"Chat",
|
||||||
|
"ChatMember",
|
||||||
|
"Document",
|
||||||
|
"File",
|
||||||
|
"ForumTopic",
|
||||||
|
"Message",
|
||||||
|
"MessageReply",
|
||||||
|
"PhotoSize",
|
||||||
|
"Sticker",
|
||||||
|
"Update",
|
||||||
|
"User",
|
||||||
|
"Video",
|
||||||
|
"Voice",
|
||||||
|
"decode_update",
|
||||||
|
"decode_updates",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class User(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
id: int
|
||||||
|
is_bot: bool | None = None
|
||||||
|
username: str | None = None
|
||||||
|
first_name: str | None = None
|
||||||
|
last_name: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class Chat(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
id: int
|
||||||
|
type: str
|
||||||
|
title: str | None = None
|
||||||
|
username: str | None = None
|
||||||
|
first_name: str | None = None
|
||||||
|
last_name: str | None = None
|
||||||
|
is_forum: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoSize(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
file_id: str
|
||||||
|
width: int
|
||||||
|
height: int
|
||||||
|
file_size: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class Document(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
file_id: str
|
||||||
|
file_name: str | None = None
|
||||||
|
mime_type: str | None = None
|
||||||
|
file_size: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class Video(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
file_id: str
|
||||||
|
file_name: str | None = None
|
||||||
|
mime_type: str | None = None
|
||||||
|
file_size: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class Voice(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
file_id: str
|
||||||
|
duration: int | None = None
|
||||||
|
mime_type: str | None = None
|
||||||
|
file_size: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class Sticker(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
file_id: str
|
||||||
|
file_size: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class MessageReply(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
message_id: int
|
||||||
|
text: str | None = None
|
||||||
|
from_: User | None = msgspec.field(default=None, name="from")
|
||||||
|
|
||||||
|
|
||||||
|
class Message(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
message_id: int
|
||||||
|
chat: Chat
|
||||||
|
message_thread_id: int | None = None
|
||||||
|
from_: User | None = msgspec.field(default=None, name="from")
|
||||||
|
text: str | None = None
|
||||||
|
caption: str | None = None
|
||||||
|
reply_to_message: MessageReply | None = None
|
||||||
|
forward_from: User | None = None
|
||||||
|
forward_from_chat: Chat | None = None
|
||||||
|
forward_from_message_id: int | None = None
|
||||||
|
forward_sender_name: str | None = None
|
||||||
|
forward_signature: str | None = None
|
||||||
|
forward_date: int | None = None
|
||||||
|
media_group_id: str | None = None
|
||||||
|
is_automatic_forward: bool | None = None
|
||||||
|
is_topic_message: bool | None = None
|
||||||
|
voice: Voice | None = None
|
||||||
|
document: Document | None = None
|
||||||
|
video: Video | None = None
|
||||||
|
photo: list[PhotoSize] | None = None
|
||||||
|
sticker: Sticker | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackQueryMessage(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
message_id: int
|
||||||
|
chat: Chat
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackQuery(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
id: str
|
||||||
|
from_: User = msgspec.field(name="from")
|
||||||
|
message: CallbackQueryMessage | None = None
|
||||||
|
data: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class Update(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
update_id: int
|
||||||
|
message: Message | None = None
|
||||||
|
callback_query: CallbackQuery | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class File(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
file_path: str
|
||||||
|
|
||||||
|
|
||||||
|
class ChatMember(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
status: str
|
||||||
|
can_manage_topics: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class ForumTopic(msgspec.Struct, forbid_unknown_fields=False):
|
||||||
|
message_thread_id: int
|
||||||
|
|
||||||
|
|
||||||
|
_UPDATE_DECODER = msgspec.json.Decoder(Update)
|
||||||
|
_UPDATES_DECODER = msgspec.json.Decoder(list[Update])
|
||||||
|
|
||||||
|
|
||||||
|
def decode_update(payload: str | bytes) -> Update:
|
||||||
|
return _UPDATE_DECODER.decode(payload)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_updates(payload: str | bytes) -> list[Update]:
|
||||||
|
return _UPDATES_DECODER.decode(payload)
|
||||||
@@ -264,35 +264,25 @@ async def wait_for_chat(token: str) -> ChatInfo:
|
|||||||
continue
|
continue
|
||||||
if not updates:
|
if not updates:
|
||||||
continue
|
continue
|
||||||
offset = updates[-1].update_id + 1
|
|
||||||
update = updates[-1]
|
update = updates[-1]
|
||||||
|
offset = update.update_id + 1
|
||||||
msg = update.message
|
msg = update.message
|
||||||
if not isinstance(msg, dict):
|
if msg is None:
|
||||||
continue
|
continue
|
||||||
sender = msg.get("from")
|
sender = msg.from_
|
||||||
if isinstance(sender, dict) and sender.get("is_bot") is True:
|
if sender is not None and sender.is_bot is True:
|
||||||
continue
|
continue
|
||||||
chat = msg.get("chat")
|
chat = msg.chat
|
||||||
if not isinstance(chat, dict):
|
if chat is None:
|
||||||
continue
|
|
||||||
chat_id = chat.get("id")
|
|
||||||
if not isinstance(chat_id, int):
|
|
||||||
continue
|
continue
|
||||||
|
chat_id = chat.id
|
||||||
return ChatInfo(
|
return ChatInfo(
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
username=chat.get("username")
|
username=chat.username,
|
||||||
if isinstance(chat.get("username"), str)
|
title=chat.title,
|
||||||
else None,
|
first_name=chat.first_name,
|
||||||
title=chat.get("title") if isinstance(chat.get("title"), str) else None,
|
last_name=chat.last_name,
|
||||||
first_name=chat.get("first_name")
|
chat_type=chat.type,
|
||||||
if isinstance(chat.get("first_name"), str)
|
|
||||||
else None,
|
|
||||||
last_name=chat.get("last_name")
|
|
||||||
if isinstance(chat.get("last_name"), str)
|
|
||||||
else None,
|
|
||||||
chat_type=chat.get("type")
|
|
||||||
if isinstance(chat.get("type"), str)
|
|
||||||
else None,
|
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
await bot.close()
|
await bot.close()
|
||||||
|
|||||||
+117
-179
@@ -1,12 +1,20 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
from collections.abc import AsyncIterator, Callable, Iterable
|
from collections.abc import AsyncIterator, Callable, Iterable
|
||||||
|
|
||||||
import anyio
|
import anyio
|
||||||
|
import msgspec
|
||||||
|
|
||||||
from ..logging import get_logger
|
from ..logging import get_logger
|
||||||
from .api_models import Update
|
from .api_schemas import (
|
||||||
|
CallbackQuery,
|
||||||
|
Document,
|
||||||
|
Message,
|
||||||
|
PhotoSize,
|
||||||
|
Sticker,
|
||||||
|
Update,
|
||||||
|
Video,
|
||||||
|
)
|
||||||
from .client_api import BotClient
|
from .client_api import BotClient
|
||||||
from .types import (
|
from .types import (
|
||||||
TelegramCallbackQuery,
|
TelegramCallbackQuery,
|
||||||
@@ -20,23 +28,20 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def parse_incoming_update(
|
def parse_incoming_update(
|
||||||
update: Update | dict[str, Any],
|
update: Update,
|
||||||
*,
|
*,
|
||||||
chat_id: int | None = None,
|
chat_id: int | None = None,
|
||||||
chat_ids: set[int] | None = None,
|
chat_ids: set[int] | None = None,
|
||||||
) -> TelegramIncomingUpdate | None:
|
) -> TelegramIncomingUpdate | None:
|
||||||
if isinstance(update, Update):
|
if update.message is not None:
|
||||||
msg = update.message
|
return _parse_incoming_message(
|
||||||
callback_query = update.callback_query
|
update.message,
|
||||||
else:
|
chat_id=chat_id,
|
||||||
msg = update.get("message")
|
chat_ids=chat_ids,
|
||||||
callback_query = update.get("callback_query")
|
)
|
||||||
|
if update.callback_query is not None:
|
||||||
if isinstance(msg, dict):
|
|
||||||
return _parse_incoming_message(msg, chat_id=chat_id, chat_ids=chat_ids)
|
|
||||||
if isinstance(callback_query, dict):
|
|
||||||
return _parse_callback_query(
|
return _parse_callback_query(
|
||||||
callback_query,
|
update.callback_query,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
chat_ids=chat_ids,
|
chat_ids=chat_ids,
|
||||||
)
|
)
|
||||||
@@ -44,167 +49,71 @@ def parse_incoming_update(
|
|||||||
|
|
||||||
|
|
||||||
def _parse_incoming_message(
|
def _parse_incoming_message(
|
||||||
msg: dict[str, Any],
|
msg: Message,
|
||||||
*,
|
*,
|
||||||
chat_id: int | None = None,
|
chat_id: int | None = None,
|
||||||
chat_ids: set[int] | None = None,
|
chat_ids: set[int] | None = None,
|
||||||
) -> TelegramIncomingMessage | None:
|
) -> TelegramIncomingMessage | None:
|
||||||
def _parse_document_payload(payload: dict[str, Any]) -> TelegramDocument | None:
|
raw_text = msg.text
|
||||||
file_id = payload.get("file_id")
|
caption = msg.caption
|
||||||
if not isinstance(file_id, str) or not file_id:
|
text = raw_text if raw_text is not None else caption
|
||||||
return None
|
|
||||||
return TelegramDocument(
|
|
||||||
file_id=file_id,
|
|
||||||
file_name=payload.get("file_name")
|
|
||||||
if isinstance(payload.get("file_name"), str)
|
|
||||||
else None,
|
|
||||||
mime_type=payload.get("mime_type")
|
|
||||||
if isinstance(payload.get("mime_type"), str)
|
|
||||||
else None,
|
|
||||||
file_size=payload.get("file_size")
|
|
||||||
if isinstance(payload.get("file_size"), int)
|
|
||||||
and not isinstance(payload.get("file_size"), bool)
|
|
||||||
else None,
|
|
||||||
raw=payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
raw_text = msg.get("text")
|
|
||||||
text = raw_text if isinstance(raw_text, str) else None
|
|
||||||
caption = msg.get("caption")
|
|
||||||
if text is None and isinstance(caption, str):
|
|
||||||
text = caption
|
|
||||||
if text is None:
|
if text is None:
|
||||||
text = ""
|
text = ""
|
||||||
file_command = False
|
file_command = False
|
||||||
if isinstance(text, str):
|
stripped = text.lstrip()
|
||||||
stripped = text.lstrip()
|
if stripped.startswith("/"):
|
||||||
if stripped.startswith("/"):
|
token = stripped.split(maxsplit=1)[0]
|
||||||
token = stripped.split(maxsplit=1)[0]
|
file_command = token.startswith("/file")
|
||||||
file_command = token.startswith("/file")
|
|
||||||
voice_payload: TelegramVoice | None = None
|
voice_payload: TelegramVoice | None = None
|
||||||
voice = msg.get("voice")
|
if msg.voice is not None:
|
||||||
if isinstance(voice, dict):
|
voice_payload = TelegramVoice(
|
||||||
file_id = voice.get("file_id")
|
file_id=msg.voice.file_id,
|
||||||
if not isinstance(file_id, str) or not file_id:
|
mime_type=msg.voice.mime_type,
|
||||||
file_id = None
|
file_size=msg.voice.file_size,
|
||||||
if file_id is not None:
|
duration=msg.voice.duration,
|
||||||
voice_payload = TelegramVoice(
|
raw=msgspec.to_builtins(msg.voice),
|
||||||
file_id=file_id,
|
)
|
||||||
mime_type=voice.get("mime_type")
|
if raw_text is None and caption is None:
|
||||||
if isinstance(voice.get("mime_type"), str)
|
text = ""
|
||||||
else None,
|
|
||||||
file_size=voice.get("file_size")
|
|
||||||
if isinstance(voice.get("file_size"), int)
|
|
||||||
and not isinstance(voice.get("file_size"), bool)
|
|
||||||
else None,
|
|
||||||
duration=voice.get("duration")
|
|
||||||
if isinstance(voice.get("duration"), int)
|
|
||||||
and not isinstance(voice.get("duration"), bool)
|
|
||||||
else None,
|
|
||||||
raw=voice,
|
|
||||||
)
|
|
||||||
if not isinstance(raw_text, str) and not isinstance(caption, str):
|
|
||||||
text = ""
|
|
||||||
document_payload: TelegramDocument | None = None
|
document_payload: TelegramDocument | None = None
|
||||||
document = msg.get("document")
|
if msg.document is not None:
|
||||||
if isinstance(document, dict):
|
document_payload = _document_from_media(msg.document)
|
||||||
document_payload = _parse_document_payload(document)
|
if document_payload is None and msg.video is not None:
|
||||||
|
document_payload = _document_from_media(msg.video)
|
||||||
if document_payload is None:
|
if document_payload is None:
|
||||||
video = msg.get("video")
|
best = _best_photo(msg.photo)
|
||||||
if isinstance(video, dict):
|
if best is not None:
|
||||||
document_payload = _parse_document_payload(video)
|
document_payload = _document_from_photo(best)
|
||||||
if document_payload is None:
|
if document_payload is None and file_command and msg.sticker is not None:
|
||||||
photo = msg.get("photo")
|
document_payload = _document_from_sticker(msg.sticker)
|
||||||
if isinstance(photo, list):
|
has_text = raw_text is not None or caption is not None
|
||||||
best: dict[str, Any] | None = None
|
|
||||||
best_score = -1
|
|
||||||
for item in photo:
|
|
||||||
if not isinstance(item, dict):
|
|
||||||
continue
|
|
||||||
file_id = item.get("file_id")
|
|
||||||
if not isinstance(file_id, str) or not file_id:
|
|
||||||
continue
|
|
||||||
size = item.get("file_size")
|
|
||||||
if isinstance(size, int) and not isinstance(size, bool):
|
|
||||||
score = size
|
|
||||||
else:
|
|
||||||
width = item.get("width")
|
|
||||||
height = item.get("height")
|
|
||||||
if isinstance(width, int) and isinstance(height, int):
|
|
||||||
score = width * height
|
|
||||||
else:
|
|
||||||
score = 0
|
|
||||||
if score > best_score:
|
|
||||||
best_score = score
|
|
||||||
best = item
|
|
||||||
if best is not None:
|
|
||||||
document_payload = _parse_document_payload(best)
|
|
||||||
if document_payload is None and file_command:
|
|
||||||
sticker = msg.get("sticker")
|
|
||||||
if isinstance(sticker, dict):
|
|
||||||
document_payload = _parse_document_payload(sticker)
|
|
||||||
has_text = isinstance(raw_text, str) or isinstance(caption, str)
|
|
||||||
if not has_text and voice_payload is None and document_payload is None:
|
if not has_text and voice_payload is None and document_payload is None:
|
||||||
return None
|
return None
|
||||||
chat = msg.get("chat")
|
msg_chat_id = msg.chat.id
|
||||||
if not isinstance(chat, dict):
|
chat_type = msg.chat.type
|
||||||
return None
|
is_forum = msg.chat.is_forum
|
||||||
msg_chat_id = chat.get("id")
|
|
||||||
if not isinstance(msg_chat_id, int):
|
|
||||||
return None
|
|
||||||
chat_type = chat.get("type") if isinstance(chat.get("type"), str) else None
|
|
||||||
is_forum = chat.get("is_forum")
|
|
||||||
if not isinstance(is_forum, bool):
|
|
||||||
is_forum = None
|
|
||||||
allowed = chat_ids
|
allowed = chat_ids
|
||||||
if allowed is None and chat_id is not None:
|
if allowed is None and chat_id is not None:
|
||||||
allowed = {chat_id}
|
allowed = {chat_id}
|
||||||
if allowed is not None and msg_chat_id not in allowed:
|
if allowed is not None and msg_chat_id not in allowed:
|
||||||
return None
|
return None
|
||||||
message_id = msg.get("message_id")
|
reply = msg.reply_to_message
|
||||||
if not isinstance(message_id, int):
|
reply_to_message_id = reply.message_id if reply is not None else None
|
||||||
return None
|
reply_to_text = reply.text if reply is not None else None
|
||||||
reply = msg.get("reply_to_message")
|
reply_to_is_bot = (
|
||||||
reply_to_message_id = None
|
reply.from_.is_bot if reply is not None and reply.from_ is not None else None
|
||||||
reply_to_text = None
|
|
||||||
reply_to_is_bot = None
|
|
||||||
reply_to_username = None
|
|
||||||
if isinstance(reply, dict):
|
|
||||||
reply_to_message_id = (
|
|
||||||
reply.get("message_id")
|
|
||||||
if isinstance(reply.get("message_id"), int)
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
reply_to_text = (
|
|
||||||
reply.get("text") if isinstance(reply.get("text"), str) else None
|
|
||||||
)
|
|
||||||
reply_from = reply.get("from")
|
|
||||||
if isinstance(reply_from, dict):
|
|
||||||
is_bot = reply_from.get("is_bot")
|
|
||||||
if isinstance(is_bot, bool):
|
|
||||||
reply_to_is_bot = is_bot
|
|
||||||
username = reply_from.get("username")
|
|
||||||
if isinstance(username, str):
|
|
||||||
reply_to_username = username
|
|
||||||
sender = msg.get("from")
|
|
||||||
sender_id = (
|
|
||||||
sender.get("id")
|
|
||||||
if isinstance(sender, dict) and isinstance(sender.get("id"), int)
|
|
||||||
else None
|
|
||||||
)
|
)
|
||||||
media_group_id = msg.get("media_group_id")
|
reply_to_username = (
|
||||||
if not isinstance(media_group_id, str):
|
reply.from_.username if reply is not None and reply.from_ is not None else None
|
||||||
media_group_id = None
|
)
|
||||||
thread_id = msg.get("message_thread_id")
|
sender_id = msg.from_.id if msg.from_ is not None else None
|
||||||
if isinstance(thread_id, bool) or not isinstance(thread_id, int):
|
media_group_id = msg.media_group_id
|
||||||
thread_id = None
|
thread_id = msg.message_thread_id
|
||||||
is_topic_message = msg.get("is_topic_message")
|
is_topic_message = msg.is_topic_message
|
||||||
if not isinstance(is_topic_message, bool):
|
|
||||||
is_topic_message = None
|
|
||||||
return TelegramIncomingMessage(
|
return TelegramIncomingMessage(
|
||||||
transport="telegram",
|
transport="telegram",
|
||||||
chat_id=msg_chat_id,
|
chat_id=msg_chat_id,
|
||||||
message_id=message_id,
|
message_id=msg.message_id,
|
||||||
text=text,
|
text=text,
|
||||||
reply_to_message_id=reply_to_message_id,
|
reply_to_message_id=reply_to_message_id,
|
||||||
reply_to_text=reply_to_text,
|
reply_to_text=reply_to_text,
|
||||||
@@ -218,51 +127,80 @@ def _parse_incoming_message(
|
|||||||
is_forum=is_forum,
|
is_forum=is_forum,
|
||||||
voice=voice_payload,
|
voice=voice_payload,
|
||||||
document=document_payload,
|
document=document_payload,
|
||||||
raw=msg,
|
raw=msgspec.to_builtins(msg),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_callback_query(
|
def _parse_callback_query(
|
||||||
query: dict[str, Any],
|
query: CallbackQuery,
|
||||||
*,
|
*,
|
||||||
chat_id: int | None = None,
|
chat_id: int | None = None,
|
||||||
chat_ids: set[int] | None = None,
|
chat_ids: set[int] | None = None,
|
||||||
) -> TelegramCallbackQuery | None:
|
) -> TelegramCallbackQuery | None:
|
||||||
callback_id = query.get("id")
|
callback_id = query.id
|
||||||
if not isinstance(callback_id, str) or not callback_id:
|
msg = query.message
|
||||||
return None
|
if msg is None:
|
||||||
msg = query.get("message")
|
|
||||||
if not isinstance(msg, dict):
|
|
||||||
return None
|
|
||||||
chat = msg.get("chat")
|
|
||||||
if not isinstance(chat, dict):
|
|
||||||
return None
|
|
||||||
msg_chat_id = chat.get("id")
|
|
||||||
if not isinstance(msg_chat_id, int):
|
|
||||||
return None
|
return None
|
||||||
|
msg_chat_id = msg.chat.id
|
||||||
allowed = chat_ids
|
allowed = chat_ids
|
||||||
if allowed is None and chat_id is not None:
|
if allowed is None and chat_id is not None:
|
||||||
allowed = {chat_id}
|
allowed = {chat_id}
|
||||||
if allowed is not None and msg_chat_id not in allowed:
|
if allowed is not None and msg_chat_id not in allowed:
|
||||||
return None
|
return None
|
||||||
message_id = msg.get("message_id")
|
data = query.data
|
||||||
if not isinstance(message_id, int):
|
sender_id = query.from_.id if query.from_ is not None else None
|
||||||
return None
|
|
||||||
data = query.get("data") if isinstance(query.get("data"), str) else None
|
|
||||||
sender = query.get("from")
|
|
||||||
sender_id = (
|
|
||||||
sender.get("id")
|
|
||||||
if isinstance(sender, dict) and isinstance(sender.get("id"), int)
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
return TelegramCallbackQuery(
|
return TelegramCallbackQuery(
|
||||||
transport="telegram",
|
transport="telegram",
|
||||||
chat_id=msg_chat_id,
|
chat_id=msg_chat_id,
|
||||||
message_id=message_id,
|
message_id=msg.message_id,
|
||||||
callback_query_id=callback_id,
|
callback_query_id=callback_id,
|
||||||
data=data,
|
data=data,
|
||||||
sender_id=sender_id,
|
sender_id=sender_id,
|
||||||
raw=query,
|
raw=msgspec.to_builtins(query),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _best_photo(photos: list[PhotoSize] | None) -> PhotoSize | None:
|
||||||
|
if not photos:
|
||||||
|
return None
|
||||||
|
best = None
|
||||||
|
best_score = -1
|
||||||
|
for item in photos:
|
||||||
|
size = item.file_size
|
||||||
|
score = size if size is not None else item.width * item.height
|
||||||
|
if score > best_score:
|
||||||
|
best_score = score
|
||||||
|
best = item
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
def _document_from_media(media: Document | Video) -> TelegramDocument:
|
||||||
|
return TelegramDocument(
|
||||||
|
file_id=media.file_id,
|
||||||
|
file_name=media.file_name,
|
||||||
|
mime_type=media.mime_type,
|
||||||
|
file_size=media.file_size,
|
||||||
|
raw=msgspec.to_builtins(media),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _document_from_photo(photo: PhotoSize) -> TelegramDocument:
|
||||||
|
return TelegramDocument(
|
||||||
|
file_id=photo.file_id,
|
||||||
|
file_name=None,
|
||||||
|
mime_type=None,
|
||||||
|
file_size=photo.file_size,
|
||||||
|
raw=msgspec.to_builtins(photo),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _document_from_sticker(sticker: Sticker) -> TelegramDocument:
|
||||||
|
return TelegramDocument(
|
||||||
|
file_id=sticker.file_id,
|
||||||
|
file_name=None,
|
||||||
|
mime_type=None,
|
||||||
|
file_size=sticker.file_size,
|
||||||
|
raw=msgspec.to_builtins(sticker),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class FakeBot(BotClient):
|
|||||||
"replace_message_id": replace_message_id,
|
"replace_message_id": replace_message_id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return Message(message_id=1)
|
return Message(message_id=1, chat=Chat(id=chat_id, type="private"))
|
||||||
|
|
||||||
async def send_document(
|
async def send_document(
|
||||||
self,
|
self,
|
||||||
@@ -164,7 +164,7 @@ class FakeBot(BotClient):
|
|||||||
"caption": caption,
|
"caption": caption,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return Message(message_id=2)
|
return Message(message_id=2, chat=Chat(id=chat_id, type="private"))
|
||||||
|
|
||||||
async def edit_message_text(
|
async def edit_message_text(
|
||||||
self,
|
self,
|
||||||
@@ -188,7 +188,7 @@ class FakeBot(BotClient):
|
|||||||
"wait": wait,
|
"wait": wait,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return Message(message_id=message_id)
|
return Message(message_id=message_id, chat=Chat(id=chat_id, type="private"))
|
||||||
|
|
||||||
async def delete_message(self, chat_id: int, message_id: int) -> bool:
|
async def delete_message(self, chat_id: int, message_id: int) -> bool:
|
||||||
self.delete_calls.append({"chat_id": chat_id, "message_id": message_id})
|
self.delete_calls.append({"chat_id": chat_id, "message_id": message_id})
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from rich.text import Text
|
|||||||
|
|
||||||
from takopi.config import ConfigError
|
from takopi.config import ConfigError
|
||||||
from takopi.telegram import onboarding
|
from takopi.telegram import onboarding
|
||||||
from takopi.telegram.api_models import Update, User
|
from takopi.telegram.api_models import Chat, Message, Update, User
|
||||||
from takopi.telegram.client import TelegramRetryAfter
|
from takopi.telegram.client import TelegramRetryAfter
|
||||||
|
|
||||||
|
|
||||||
@@ -311,35 +311,57 @@ async def test_get_bot_info_gives_up(monkeypatch) -> None:
|
|||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_wait_for_chat_filters_updates(monkeypatch) -> None:
|
async def test_wait_for_chat_filters_updates(monkeypatch) -> None:
|
||||||
updates = [
|
updates = [
|
||||||
[Update(update_id=1, message={"from": {"is_bot": True}, "chat": {"id": 1}})],
|
[
|
||||||
|
Update(
|
||||||
|
update_id=1,
|
||||||
|
message=Message(
|
||||||
|
message_id=1,
|
||||||
|
from_=User(id=1, is_bot=True),
|
||||||
|
chat=Chat(id=1, type="private"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
None,
|
None,
|
||||||
[],
|
[],
|
||||||
[Update(update_id=2, message=None)],
|
[Update(update_id=2, message=None)],
|
||||||
[
|
[
|
||||||
Update(
|
Update(
|
||||||
update_id=3,
|
update_id=3,
|
||||||
message={"from": {"is_bot": True}, "chat": {"id": 2}},
|
message=Message(
|
||||||
|
message_id=3,
|
||||||
|
from_=User(id=2, is_bot=True),
|
||||||
|
chat=Chat(id=2, type="private"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
Update(
|
Update(
|
||||||
update_id=4,
|
update_id=4,
|
||||||
message={"from": {"is_bot": False}, "chat": "nope"},
|
message=Message(
|
||||||
|
message_id=4,
|
||||||
|
from_=User(id=3, is_bot=True),
|
||||||
|
chat=Chat(id=3, type="private"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
Update(
|
Update(
|
||||||
update_id=5,
|
update_id=5,
|
||||||
message={"from": {"is_bot": False}, "chat": {"id": "bad"}},
|
message=Message(
|
||||||
|
message_id=5,
|
||||||
|
from_=User(id=4, is_bot=True),
|
||||||
|
chat=Chat(id=4, type="private"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
Update(
|
Update(
|
||||||
update_id=6,
|
update_id=6,
|
||||||
message={
|
message=Message(
|
||||||
"from": {"is_bot": False},
|
message_id=6,
|
||||||
"chat": {"id": 7, "username": "bob", "type": "private"},
|
from_=User(id=5, is_bot=False),
|
||||||
},
|
chat=Chat(id=7, username="bob", type="private"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from takopi.telegram.commands.topics import _handle_topic_command
|
|||||||
import takopi.telegram.loop as telegram_loop
|
import takopi.telegram.loop as telegram_loop
|
||||||
import takopi.telegram.topics as telegram_topics
|
import takopi.telegram.topics as telegram_topics
|
||||||
from takopi.directives import parse_directives
|
from takopi.directives import parse_directives
|
||||||
from takopi.telegram.api_models import File, ForumTopic, Message, Update, User
|
from takopi.telegram.api_models import Chat, File, ForumTopic, Message, Update, User
|
||||||
from takopi.settings import TelegramFilesSettings, TelegramTopicsSettings
|
from takopi.settings import TelegramFilesSettings, TelegramTopicsSettings
|
||||||
from takopi.telegram.bridge import (
|
from takopi.telegram.bridge import (
|
||||||
TelegramBridgeConfig,
|
TelegramBridgeConfig,
|
||||||
@@ -462,7 +462,7 @@ async def test_telegram_transport_edit_wait_false_returns_ref() -> None:
|
|||||||
)
|
)
|
||||||
if not wait:
|
if not wait:
|
||||||
return None
|
return None
|
||||||
return Message(message_id=message_id)
|
return Message(message_id=message_id, chat=Chat(id=chat_id, type="private"))
|
||||||
|
|
||||||
async def delete_message(
|
async def delete_message(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ async def test_client_methods_build_params_and_decode() -> None:
|
|||||||
payloads = {
|
payloads = {
|
||||||
"getUpdates": [{"update_id": 1}],
|
"getUpdates": [{"update_id": 1}],
|
||||||
"getFile": {"file_path": "path"},
|
"getFile": {"file_path": "path"},
|
||||||
"sendMessage": {"message_id": 1},
|
"sendMessage": {"message_id": 1, "chat": {"id": 1, "type": "private"}},
|
||||||
"sendDocument": {"message_id": 2},
|
"sendDocument": {"message_id": 2, "chat": {"id": 1, "type": "private"}},
|
||||||
"editMessageText": {"message_id": 3},
|
"editMessageText": {"message_id": 3, "chat": {"id": 1, "type": "private"}},
|
||||||
"deleteMessage": True,
|
"deleteMessage": True,
|
||||||
"setMyCommands": True,
|
"setMyCommands": True,
|
||||||
"getMe": {"id": 7},
|
"getMe": {"id": 7},
|
||||||
|
|||||||
+156
-149
@@ -3,23 +3,37 @@ from takopi.telegram import (
|
|||||||
TelegramIncomingMessage,
|
TelegramIncomingMessage,
|
||||||
parse_incoming_update,
|
parse_incoming_update,
|
||||||
)
|
)
|
||||||
|
from takopi.telegram.api_models import (
|
||||||
|
CallbackQuery,
|
||||||
|
CallbackQueryMessage,
|
||||||
|
Chat,
|
||||||
|
Document,
|
||||||
|
Message,
|
||||||
|
MessageReply,
|
||||||
|
PhotoSize,
|
||||||
|
Sticker,
|
||||||
|
Update,
|
||||||
|
User,
|
||||||
|
Video,
|
||||||
|
Voice,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_maps_fields() -> None:
|
def test_parse_incoming_update_maps_fields() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"message": {
|
message=Message(
|
||||||
"message_id": 10,
|
message_id=10,
|
||||||
"text": "hello",
|
text="hello",
|
||||||
"chat": {"id": 123, "type": "supergroup", "is_forum": True},
|
chat=Chat(id=123, type="supergroup", is_forum=True),
|
||||||
"from": {"id": 99},
|
from_=User(id=99),
|
||||||
"reply_to_message": {
|
reply_to_message=MessageReply(
|
||||||
"message_id": 5,
|
message_id=5,
|
||||||
"text": "prev",
|
text="prev",
|
||||||
"from": {"id": 77, "is_bot": True, "username": "ReplyBot"},
|
from_=User(id=77, is_bot=True, username="ReplyBot"),
|
||||||
},
|
),
|
||||||
},
|
),
|
||||||
}
|
)
|
||||||
|
|
||||||
msg = parse_incoming_update(update, chat_id=123)
|
msg = parse_incoming_update(update, chat_id=123)
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
@@ -39,50 +53,49 @@ def test_parse_incoming_update_maps_fields() -> None:
|
|||||||
assert msg.is_forum is True
|
assert msg.is_forum is True
|
||||||
assert msg.voice is None
|
assert msg.voice is None
|
||||||
assert msg.document is None
|
assert msg.document is None
|
||||||
assert msg.raw == update["message"]
|
assert msg.raw
|
||||||
|
assert msg.raw["message_id"] == 10
|
||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_filters_non_matching_chat() -> None:
|
def test_parse_incoming_update_filters_non_matching_chat() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"message": {
|
message=Message(
|
||||||
"message_id": 10,
|
message_id=10,
|
||||||
"text": "hello",
|
text="hello",
|
||||||
"chat": {"id": 123},
|
chat=Chat(id=123, type="private"),
|
||||||
},
|
),
|
||||||
}
|
)
|
||||||
|
|
||||||
assert parse_incoming_update(update, chat_id=999) is None
|
assert parse_incoming_update(update, chat_id=999) is None
|
||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_filters_non_text_and_non_voice() -> None:
|
def test_parse_incoming_update_filters_non_text_and_non_voice() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"message": {
|
message=Message(
|
||||||
"message_id": 10,
|
message_id=10,
|
||||||
"chat": {"id": 123},
|
chat=Chat(id=123, type="private"),
|
||||||
"location": {"latitude": 1.0, "longitude": 2.0},
|
),
|
||||||
},
|
)
|
||||||
}
|
|
||||||
|
|
||||||
assert parse_incoming_update(update, chat_id=123) is None
|
assert parse_incoming_update(update, chat_id=123) is None
|
||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_voice_message() -> None:
|
def test_parse_incoming_update_voice_message() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"message": {
|
message=Message(
|
||||||
"message_id": 10,
|
message_id=10,
|
||||||
"chat": {"id": 123},
|
chat=Chat(id=123, type="private"),
|
||||||
"voice": {
|
voice=Voice(
|
||||||
"file_id": "voice-id",
|
file_id="voice-id",
|
||||||
"file_unique_id": "uniq",
|
duration=3,
|
||||||
"duration": 3,
|
mime_type="audio/ogg",
|
||||||
"mime_type": "audio/ogg",
|
file_size=1234,
|
||||||
"file_size": 1234,
|
),
|
||||||
},
|
),
|
||||||
},
|
)
|
||||||
}
|
|
||||||
|
|
||||||
msg = parse_incoming_update(update, chat_id=123)
|
msg = parse_incoming_update(update, chat_id=123)
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
@@ -96,21 +109,20 @@ def test_parse_incoming_update_voice_message() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_document_message() -> None:
|
def test_parse_incoming_update_document_message() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"message": {
|
message=Message(
|
||||||
"message_id": 10,
|
message_id=10,
|
||||||
"caption": "/file put incoming/doc.txt",
|
caption="/file put incoming/doc.txt",
|
||||||
"chat": {"id": 123},
|
chat=Chat(id=123, type="private"),
|
||||||
"document": {
|
document=Document(
|
||||||
"file_id": "doc-id",
|
file_id="doc-id",
|
||||||
"file_unique_id": "uniq",
|
file_name="doc.txt",
|
||||||
"file_name": "doc.txt",
|
mime_type="text/plain",
|
||||||
"mime_type": "text/plain",
|
file_size=4321,
|
||||||
"file_size": 4321,
|
),
|
||||||
},
|
),
|
||||||
},
|
)
|
||||||
}
|
|
||||||
|
|
||||||
msg = parse_incoming_update(update, chat_id=123)
|
msg = parse_incoming_update(update, chat_id=123)
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
@@ -124,30 +136,28 @@ def test_parse_incoming_update_document_message() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_photo_message() -> None:
|
def test_parse_incoming_update_photo_message() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"message": {
|
message=Message(
|
||||||
"message_id": 10,
|
message_id=10,
|
||||||
"caption": "/file put incoming/photo.jpg",
|
caption="/file put incoming/photo.jpg",
|
||||||
"chat": {"id": 123},
|
chat=Chat(id=123, type="private"),
|
||||||
"photo": [
|
photo=[
|
||||||
{
|
PhotoSize(
|
||||||
"file_id": "small",
|
file_id="small",
|
||||||
"file_unique_id": "uniq-small",
|
file_size=100,
|
||||||
"file_size": 100,
|
width=90,
|
||||||
"width": 90,
|
height=90,
|
||||||
"height": 90,
|
),
|
||||||
},
|
PhotoSize(
|
||||||
{
|
file_id="large",
|
||||||
"file_id": "large",
|
file_size=1000,
|
||||||
"file_unique_id": "uniq-large",
|
width=800,
|
||||||
"file_size": 1000,
|
height=600,
|
||||||
"width": 800,
|
),
|
||||||
"height": 600,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
),
|
||||||
}
|
)
|
||||||
|
|
||||||
msg = parse_incoming_update(update, chat_id=123)
|
msg = parse_incoming_update(update, chat_id=123)
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
@@ -160,23 +170,22 @@ def test_parse_incoming_update_photo_message() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_media_group_id() -> None:
|
def test_parse_incoming_update_media_group_id() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"message": {
|
message=Message(
|
||||||
"message_id": 10,
|
message_id=10,
|
||||||
"chat": {"id": 123},
|
chat=Chat(id=123, type="private"),
|
||||||
"media_group_id": "group-1",
|
media_group_id="group-1",
|
||||||
"photo": [
|
photo=[
|
||||||
{
|
PhotoSize(
|
||||||
"file_id": "large",
|
file_id="large",
|
||||||
"file_unique_id": "uniq-large",
|
file_size=1000,
|
||||||
"file_size": 1000,
|
width=800,
|
||||||
"width": 800,
|
height=600,
|
||||||
"height": 600,
|
)
|
||||||
}
|
|
||||||
],
|
],
|
||||||
},
|
),
|
||||||
}
|
)
|
||||||
|
|
||||||
msg = parse_incoming_update(update, chat_id=123)
|
msg = parse_incoming_update(update, chat_id=123)
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
@@ -185,21 +194,20 @@ def test_parse_incoming_update_media_group_id() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_video_message() -> None:
|
def test_parse_incoming_update_video_message() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"message": {
|
message=Message(
|
||||||
"message_id": 10,
|
message_id=10,
|
||||||
"caption": "/file put incoming/video.mp4",
|
caption="/file put incoming/video.mp4",
|
||||||
"chat": {"id": 123},
|
chat=Chat(id=123, type="private"),
|
||||||
"video": {
|
video=Video(
|
||||||
"file_id": "video-id",
|
file_id="video-id",
|
||||||
"file_unique_id": "uniq",
|
file_name="video.mp4",
|
||||||
"file_name": "video.mp4",
|
mime_type="video/mp4",
|
||||||
"mime_type": "video/mp4",
|
file_size=4242,
|
||||||
"file_size": 4242,
|
),
|
||||||
},
|
),
|
||||||
},
|
)
|
||||||
}
|
|
||||||
|
|
||||||
msg = parse_incoming_update(update, chat_id=123)
|
msg = parse_incoming_update(update, chat_id=123)
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
@@ -213,19 +221,18 @@ def test_parse_incoming_update_video_message() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_sticker_message() -> None:
|
def test_parse_incoming_update_sticker_message() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"message": {
|
message=Message(
|
||||||
"message_id": 10,
|
message_id=10,
|
||||||
"caption": "/file put incoming/sticker.webp",
|
caption="/file put incoming/sticker.webp",
|
||||||
"chat": {"id": 123},
|
chat=Chat(id=123, type="private"),
|
||||||
"sticker": {
|
sticker=Sticker(
|
||||||
"file_id": "sticker-id",
|
file_id="sticker-id",
|
||||||
"file_unique_id": "uniq",
|
file_size=2468,
|
||||||
"file_size": 2468,
|
),
|
||||||
},
|
),
|
||||||
},
|
)
|
||||||
}
|
|
||||||
|
|
||||||
msg = parse_incoming_update(update, chat_id=123)
|
msg = parse_incoming_update(update, chat_id=123)
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
@@ -239,18 +246,18 @@ def test_parse_incoming_update_sticker_message() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_callback_query() -> None:
|
def test_parse_incoming_update_callback_query() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"callback_query": {
|
callback_query=CallbackQuery(
|
||||||
"id": "cbq-1",
|
id="cbq-1",
|
||||||
"data": "takopi:cancel",
|
data="takopi:cancel",
|
||||||
"from": {"id": 321},
|
from_=User(id=321),
|
||||||
"message": {
|
message=CallbackQueryMessage(
|
||||||
"message_id": 55,
|
message_id=55,
|
||||||
"chat": {"id": 123},
|
chat=Chat(id=123, type="private"),
|
||||||
},
|
),
|
||||||
},
|
),
|
||||||
}
|
)
|
||||||
|
|
||||||
msg = parse_incoming_update(update, chat_id=123)
|
msg = parse_incoming_update(update, chat_id=123)
|
||||||
assert isinstance(msg, TelegramCallbackQuery)
|
assert isinstance(msg, TelegramCallbackQuery)
|
||||||
@@ -263,16 +270,16 @@ def test_parse_incoming_update_callback_query() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_parse_incoming_update_topic_fields() -> None:
|
def test_parse_incoming_update_topic_fields() -> None:
|
||||||
update = {
|
update = Update(
|
||||||
"update_id": 1,
|
update_id=1,
|
||||||
"message": {
|
message=Message(
|
||||||
"message_id": 10,
|
message_id=10,
|
||||||
"text": "hello",
|
text="hello",
|
||||||
"message_thread_id": 77,
|
message_thread_id=77,
|
||||||
"is_topic_message": True,
|
is_topic_message=True,
|
||||||
"chat": {"id": -100, "type": "supergroup", "is_forum": True},
|
chat=Chat(id=-100, type="supergroup", is_forum=True),
|
||||||
},
|
),
|
||||||
}
|
)
|
||||||
|
|
||||||
msg = parse_incoming_update(update, chat_id=-100)
|
msg = parse_incoming_update(update, chat_id=-100)
|
||||||
assert isinstance(msg, TelegramIncomingMessage)
|
assert isinstance(msg, TelegramIncomingMessage)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Any
|
|||||||
import anyio
|
import anyio
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from takopi.telegram.api_models import File, Message, Update, User
|
from takopi.telegram.api_models import Chat, File, Message, Update, User
|
||||||
from takopi.telegram.client import BotClient, TelegramClient, TelegramRetryAfter
|
from takopi.telegram.client import BotClient, TelegramClient, TelegramRetryAfter
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class FakeBot(BotClient):
|
|||||||
_ = reply_markup
|
_ = reply_markup
|
||||||
_ = replace_message_id
|
_ = replace_message_id
|
||||||
self.calls.append("send_message")
|
self.calls.append("send_message")
|
||||||
return Message(message_id=1)
|
return Message(message_id=1, chat=Chat(id=chat_id, type="private"))
|
||||||
|
|
||||||
async def send_document(
|
async def send_document(
|
||||||
self,
|
self,
|
||||||
@@ -61,7 +61,7 @@ class FakeBot(BotClient):
|
|||||||
caption,
|
caption,
|
||||||
)
|
)
|
||||||
self.calls.append("send_document")
|
self.calls.append("send_document")
|
||||||
return Message(message_id=1)
|
return Message(message_id=1, chat=Chat(id=chat_id, type="private"))
|
||||||
|
|
||||||
async def edit_message_text(
|
async def edit_message_text(
|
||||||
self,
|
self,
|
||||||
@@ -86,7 +86,7 @@ class FakeBot(BotClient):
|
|||||||
self._edit_attempts += 1
|
self._edit_attempts += 1
|
||||||
raise TelegramRetryAfter(self.retry_after)
|
raise TelegramRetryAfter(self.retry_after)
|
||||||
self._edit_attempts += 1
|
self._edit_attempts += 1
|
||||||
return Message(message_id=message_id)
|
return Message(message_id=message_id, chat=Chat(id=chat_id, type="private"))
|
||||||
|
|
||||||
async def delete_message(
|
async def delete_message(
|
||||||
self,
|
self,
|
||||||
|
|||||||
Reference in New Issue
Block a user