diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 07f2f57..b4e89f7 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -87,6 +87,15 @@ def _make_request(token, method_name, method='get', params=None, files=None): logger.debug("Request: method={0} url={1} params={2} files={3}".format(method, request_url, params, files).replace(token, token.split(':')[0] + ":{TOKEN}")) read_timeout = READ_TIMEOUT connect_timeout = CONNECT_TIMEOUT + + if files: + files_copy = dict(files) + # process types.InputFile + for key, value in files_copy.items(): + if isinstance(value, types.InputFile): + files[key] = value.file + + if files and format_header_param: fields.format_header_param = _no_encode(format_header_param) if params: diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 843ee0b..8141657 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -57,7 +57,7 @@ class SessionManager: session_manager = SessionManager() async def _process_request(token, url, method='get', params=None, files=None, request_timeout=None): - params = prepare_data(params, files) + params = _prepare_data(params, files) if request_timeout is None: request_timeout = REQUEST_TIMEOUT timeout = aiohttp.ClientTimeout(total=request_timeout) @@ -83,24 +83,17 @@ async def _process_request(token, url, method='get', params=None, files=None, re if not got_result: raise RequestTimeout("Request timeout. Request: method={0} url={1} params={2} files={3} request_timeout={4}".format(method, url, params, files, request_timeout, current_try)) - - - -def prepare_file(obj): +def _prepare_file(obj): """ - returns os.path.basename for a given file - - :param obj: - :return: + Prepares file for upload. """ name = getattr(obj, 'name', None) if name and isinstance(name, str) and name[0] != '<' and name[-1] != '>': return os.path.basename(name) - -def prepare_data(params=None, files=None): +def _prepare_data(params=None, files=None): """ - prepare data for request. + Adds the parameters and files to the request. :param params: :param files: @@ -111,18 +104,20 @@ def prepare_data(params=None, files=None): if params: for key, value in params.items(): data.add_field(key, str(value)) - if files: for key, f in files.items(): if isinstance(f, tuple): if len(f) == 2: - filename, fileobj = f + file_name, file = f else: raise ValueError('Tuple must have exactly 2 elements: filename, fileobj') + elif isinstance(f, types.InputFile): + file_name = f.file_name + file = f.file else: - filename, fileobj = prepare_file(f) or key, f + file_name, file = _prepare_file(f) or key, f - data.add_field(key, fileobj, filename=filename) + data.add_field(key, file, filename=file_name) return data diff --git a/telebot/types.py b/telebot/types.py index 6c5a1f0..0315a36 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- +from io import IOBase import logging +import os +from pathlib import Path from typing import Dict, List, Optional, Union from abc import ABC @@ -6601,3 +6604,62 @@ class ChatAdministratorRights(JsonDeserializable, JsonSerializable, Dictionaryab +class InputFile: + """ + A class to send files through Telegram Bot API. + + You need to pass a file, which should be an instance of :class:`io.IOBase` or + :class:`pathlib.Path`, or :obj:`str`. + + If you pass an :obj:`str` as a file, it will be opened and closed by the class. + + :param file: A file to send. + :type file: :class:`io.IOBase` or :class:`pathlib.Path` or :obj:`str` + + .. code-block:: python3 + :caption: Example on sending a file using this class + + from telebot.types import InputFile + + # Sending a file from disk + bot.send_document( + chat_id, + InputFile('/path/to/file/file.txt') + ) + + + # Sending a file from an io.IOBase object + with open('/path/to/file/file.txt', 'rb') as f: + bot.send_document( + chat_id, + InputFile(f) + ) + + # Sending a file using pathlib.Path: + bot.send_document( + chat_id, + InputFile(pathlib.Path('/path/to/file/file.txt')) + ) + """ + def __init__(self, file) -> None: + self._file, self.file_name = self._resolve_file(file) + + def _resolve_file(self, file): + if isinstance(file, str): + _file = open(file, 'rb') + return _file, os.path.basename(_file.name) + elif isinstance(file, IOBase): + return file, os.path.basename(file.name) + elif isinstance(file, Path): + _file = open(file, 'rb') + return _file, os.path.basename(_file.name) + else: + raise TypeError("File must be a string or a file-like object(pathlib.Path, io.IOBase).") + + + @property + def file(self): + """ + File object. + """ + return self._file