Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions telebot/apihelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,8 @@ def send_rich_message(
direct_messages_topic_id=None, suggested_post_parameters=None, allow_paid_broadcast=None,
message_thread_id=None, ephemeral_message_parameters=None):
method_url = r'sendRichMessage'
payload = {'chat_id': chat_id, 'rich_message': rich_message.to_json()}
rich_message_json, files = rich_message.convert_input_rich_message()
payload = {'chat_id': chat_id, 'rich_message': rich_message_json}
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if protect_content is not None:
Expand All @@ -329,18 +330,19 @@ def send_rich_message(
payload['message_thread_id'] = message_thread_id
if ephemeral_message_parameters is not None:
payload['ephemeral_message_parameters'] = ephemeral_message_parameters.to_json()
return _make_request(token, method_url, params=payload, method='post')
return _make_request(token, method_url, params=payload, files=files, method='post')

def send_rich_message_draft(token, chat_id, draft_id, rich_message, message_thread_id=None, can_stop=None, keep_on_stop=None):
method_url = r'sendRichMessageDraft'
payload = {'chat_id': str(chat_id), 'draft_id': draft_id, 'rich_message': rich_message.to_json()}
rich_message_json, files = rich_message.convert_input_rich_message()
payload = {'chat_id': str(chat_id), 'draft_id': draft_id, 'rich_message': rich_message_json}
if message_thread_id is not None:
payload['message_thread_id'] = message_thread_id
if can_stop is not None:
payload['can_stop'] = can_stop
if keep_on_stop is not None:
payload['keep_on_stop'] = keep_on_stop
return _make_request(token, method_url, params=payload, method='post')
return _make_request(token, method_url, params=payload, files=files, method='post')


def set_webhook(token, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None,
Expand Down Expand Up @@ -1875,9 +1877,10 @@ def edit_message_text(
payload['business_connection_id'] = business_connection_id
if timeout:
payload['timeout'] = timeout
files = None
if rich_message:
payload['rich_message'] = rich_message.to_json()
return _make_request(token, method_url, params=payload, method='post')
payload['rich_message'], files = rich_message.convert_input_rich_message()
return _make_request(token, method_url, params=payload, files=files, method='post')


def edit_message_caption(
Expand Down
15 changes: 9 additions & 6 deletions telebot/asyncio_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,8 @@ async def send_rich_message(
reply_parameters=None, reply_markup=None, business_connection_id=None, allow_paid_broadcast=None, direct_messages_topic_id=None,
suggested_post_parameters=None, message_thread_id=None, ephemeral_message_parameters=None):
method_url = r'sendRichMessage'
payload = {'chat_id': str(chat_id), 'rich_message': rich_message.to_json()}
rich_message_json, files = rich_message.convert_input_rich_message()
payload = {'chat_id': str(chat_id), 'rich_message': rich_message_json}
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if protect_content is not None:
Expand All @@ -362,19 +363,20 @@ async def send_rich_message(
if ephemeral_message_parameters is not None:
payload['ephemeral_message_parameters'] = ephemeral_message_parameters.to_json()

return await _process_request(token, method_url, params=payload, method='post')
return await _process_request(token, method_url, params=payload, files=files, method='post')


async def send_rich_message_draft(token, chat_id, draft_id, rich_message, message_thread_id=None, can_stop=None, keep_on_stop=None):
method_url = r'sendRichMessageDraft'
payload = {'chat_id': chat_id, 'draft_id': draft_id, 'rich_message': rich_message.to_json()}
rich_message_json, files = rich_message.convert_input_rich_message()
payload = {'chat_id': chat_id, 'draft_id': draft_id, 'rich_message': rich_message_json}
if message_thread_id is not None:
payload['message_thread_id'] = message_thread_id
if can_stop is not None:
payload['can_stop'] = can_stop
if keep_on_stop is not None:
payload['keep_on_stop'] = keep_on_stop
return await _process_request(token, method_url, params=payload, method='post')
return await _process_request(token, method_url, params=payload, files=files, method='post')


async def get_user_profile_photos(token, user_id, offset=None, limit=None):
Expand Down Expand Up @@ -1872,9 +1874,10 @@ async def edit_message_text(
payload['business_connection_id'] = business_connection_id
if timeout:
payload['timeout'] = timeout
files = None
if rich_message:
payload['rich_message'] = rich_message.to_json()
return await _process_request(token, method_url, params=payload, method='post')
payload['rich_message'], files = rich_message.convert_input_rich_message()
return await _process_request(token, method_url, params=payload, files=files, method='post')


async def edit_message_caption(
Expand Down
22 changes: 22 additions & 0 deletions telebot/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16637,6 +16637,28 @@ def to_dict(self) -> dict:
def to_json(self) -> str:
return json.dumps(self.to_dict())

def convert_input_rich_message(self):
"""
:meta private:
"""
files = {}

# Media can sit in self.media or in any block, including blocks nested
# in lists, collages, slideshows, details and quotations.
def collect(value):
if isinstance(value, InputMedia):
files.update(value.convert_input_media()[1])
elif isinstance(value, list):
for item in value:
collect(item)
elif isinstance(value, (InputRichBlock, InputRichBlockListItem, InputRichMessageMedia)):
for item in vars(value).values():
collect(item)

collect(self.media)
collect(self.blocks)
return self.to_json(), files


class Link(JsonDeserializable):
"""
Expand Down
79 changes: 79 additions & 0 deletions tests/test_rich_message_uploads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import asyncio
import io
import json

from telebot import apihelper, asyncio_helper, types


def _rich_message():
photo_file = types.InputFile(io.BytesIO(b'photo'), file_name='photo.jpg')
video_file = types.InputFile(io.BytesIO(b'video'), file_name='video.mp4')
# A photo block nested in a list item, and a media entry for tg:// links
nested_photo = types.InputRichBlockList(items=[
types.InputRichBlockListItem(blocks=[types.InputRichBlockPhoto(photo=types.InputMediaPhoto(photo_file))]),
])
media = types.InputRichMessageMedia(id='clip', media=types.InputMediaVideo(video_file))
return types.InputRichMessage(blocks=[nested_photo], media=[media]), photo_file, video_file


def _check_uploads(params, files, photo_file, video_file):
sent = json.loads(params['rich_message'])
photo_ref = sent['blocks'][0]['items'][0]['blocks'][0]['photo']['media']
video_ref = sent['media'][0]['media']['media']
assert files == {
photo_ref[len('attach://'):]: photo_file,
video_ref[len('attach://'):]: video_file,
}


def _capture(captured):
def fake_request(token, method_url, params=None, files=None, method=None, **kwargs):
captured.update(method_url=method_url, params=params, files=files, method=method)
return {}
return fake_request


def test_rich_message_without_uploads_has_no_files():
rich_message = types.InputRichMessage(html='<b>Hello</b>')
assert rich_message.convert_input_rich_message() == (rich_message.to_json(), {})


def test_rich_message_methods_upload_media_files(monkeypatch):
captured = {}
monkeypatch.setattr(apihelper, '_make_request', _capture(captured))

rich_message, photo_file, video_file = _rich_message()
apihelper.send_rich_message('token', 1, rich_message)
assert captured['method'] == 'post'
_check_uploads(captured['params'], captured['files'], photo_file, video_file)

rich_message, photo_file, video_file = _rich_message()
apihelper.send_rich_message_draft('token', 1, 2, rich_message)
_check_uploads(captured['params'], captured['files'], photo_file, video_file)

rich_message, photo_file, video_file = _rich_message()
apihelper.edit_message_text('token', chat_id=1, message_id=2, rich_message=rich_message)
_check_uploads(captured['params'], captured['files'], photo_file, video_file)


def test_async_rich_message_methods_upload_media_files(monkeypatch):
captured = {}

async def fake_process_request(token, url, method='get', params=None, files=None, **kwargs):
captured.update(url=url, params=params, files=files, method=method)
return {}

monkeypatch.setattr(asyncio_helper, '_process_request', fake_process_request)

rich_message, photo_file, video_file = _rich_message()
asyncio.run(asyncio_helper.send_rich_message('token', 1, rich_message))
assert captured['method'] == 'post'
_check_uploads(captured['params'], captured['files'], photo_file, video_file)

rich_message, photo_file, video_file = _rich_message()
asyncio.run(asyncio_helper.send_rich_message_draft('token', 1, 2, rich_message))
_check_uploads(captured['params'], captured['files'], photo_file, video_file)

rich_message, photo_file, video_file = _rich_message()
asyncio.run(asyncio_helper.edit_message_text('token', chat_id=1, message_id=2, rich_message=rich_message))
_check_uploads(captured['params'], captured['files'], photo_file, video_file)
Loading