Telegram боты позволяют автоматизировать многие процессы. Их использование не ограничивается одним чатом, по сути — бот это всего лишь интерфейс ввода-вывода, который помимо текста также может принимать и передавать файлы: изображения, видео, аудио, документы…
- Для пользователей максимальный размер файла — 1.5Гб
- Боты ограничены размером всего лишь в 50мб
Как обойти данное ограничение — под катом.
Telegram API
Раз пользователи могут загружать файлы до 1.5Гб — значит и мы можем — для этого создадим агента (назвал чтобы не путать с ботами) который будет работать в связке с нашим Telegram ботом. Для этого потребуется отдельный аккаунт и Telegram API.
Для начала идем на https://core.telegram.org и по инструкции регистрируем приложение, в итоге вы должны получить api_id и api_hash
Что делает агент?
Бот не может загружать файлы больше 50мб, но если у него есть file_id уже загруженного на сервера Telegram файла — то он может его пересылать. Так что алгоритм следующий
- Приложение, работающее на сервере через Bot API формирует файл для отправки
- Вызывает агента для загрузки файла на сервера Telegram
- Получает от агента file_id
- Пользуется загруженным файлом
Пример кода
Потребность загружать большие файлы появилась при написании @AudioTubeBot — изначально аудиофайл разбивался на части и отправлялся по частям. Функционал загрузки больших файлов было решено вынести в отдельное приложение, которое вызывается через subprocess.check_call
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from telethon import TelegramClient
from telethon.tl.types import DocumentAttributeAudio
import mimetypes
entity = 'AudioTube_bot' #имя сессии - все равно какое
api_id = 1959
api_hash = '88b68d6da53fe68c1c3541bbefc'
phone = '+79620181488'
client = TelegramClient(entity, api_id, api_hash, update_workers=None, spawn_read_thread=False)
client.connect()
if not client.is_user_authorized():
# client.send_code_request(phone) #при первом запуске - раскомментить, после авторизации для избежания FloodWait советую закомментить
client.sign_in(phone, input('Enter code: '))
client.start()
def main(argv):
file_path = argv[1]
file_name = argv[2]
chat_id = argv[3]
object_id = argv[4]
bot_name = argv[5]
duration = argv[6]
mimetypes.add_type('audio/aac','.aac')
mimetypes.add_type('audio/ogg','.ogg')
msg = client.send_file(
str(bot_name),
file_path,
caption=str(chat_id + ':' + object_id + ':' + duration),
file_name=str(file_name),
use_cache=False,
part_size_kb=512,
attributes=[DocumentAttributeAudio(
int(duration),
voice=None,
title=file_name[:-4],
performer='')]
)
client.disconnect()
return 0
if __name__ == '__main__':
import sys
main(sys.argv[0:])
Комментарии:
Вот и весь код — тут используется библиотека Telethon — при запуске программе передается путь к файлу для отправки, имя файла, chat_id — для кого предназначается данный файл), имя бота, который вызвал агента(например у меня это beta и release боты).
client.send_file
Просто загрузить файл на сервер через upload, получить file_id и передать его боту — не выйдет, file_id работает только внутри чата, в котором он был создан — чтобы наш бот мог переслать файл пользователю по file_id — агент должен переслать ему этот файл
— тогда бот получит свой file_id для этого файла и сможет распоряжаться им.
caption=str(...) — wat?!
Агент пересылает файлы только боту, добавляя комментарий в caption-у меня это:
- chat_id конечного пользователя
- длительность трека
- object_id в базе данных, к которому нужно привязать file_id, чтобы повторно не загружать файл(индексация, оптимизация и все такое)
Пример вызова в коде бота
На диске в path_file_mp3 уже сохранен файл для загрузки, вызываем подпрограмму и ждем ее завершения.
код
status = subprocess.check_call(
"python3.6 audiotubeagent36/main.py " +
path_file_mp3 + ' ' +
audio_title + '.'+ us_audio_codec +
' ' + str(chat_id) +
' ' + str(pool_object['_id']) +
' ' + config.BOT_NAME +
' ' + str(duration),shell=True)
В обработчике входящих сообщений делаем что то подобное
код
if message.content_type in ['document','audio']:
user_id = message.from_user.id
bot_settings = SafeConfigParser()
bot_settings.read(config.PATH_SETTINGS_FILE)
c_type = message.content_type
if functions.check_is_admin(bot_settings, user_id):
if c_type == 'audio':
file_id = message.audio.file_id
audio_title = message.audio.title
else:
file_id = message.document.file_id
audio_title = message.document.file_name[:-4]
client_chat_id = message.caption
if client_chat_id.find(u':') != -1:
client_chat_id, q_pool_obj_id, duration_s = re.split(r':',client_chat_id)
#добавляем file_id в базу
q_pool.update_request_file_id(str(q_pool_obj_id), str(file_id))
#пересылаем конечному адресату
bot.send_audio(int(client_chat_id),
file_id,caption='',
duration=int(duration_s),
title=audio_title,
performer='')
return
Вопросы/предложения пишите в комментариях или в чате.
Комментарии (5)
t0rr
04.02.2018 12:47Здесь у тебя дублируется запрос, поэтому и возникает FloodWait:
client.connect() if not client.is_user_authorized(): # client.send_code_request(phone) #при первом запуске - раскомментить, после авторизации для избежания FloodWait советую закомментить client.sign_in(phone, input('Enter code: ')) client.start()
Вижу, что пример ты брал из документации, однако в методе .start() клиент и так выполняет все действия, необходимые для авторизации.
Если требуется двухфакторка, то необходимо в конфиге указать пароль.
Простой и рабочий пример:
client.connect() client.start(phone=config.PHONE, password=config.PASSWORD)
SlavikMIPT Автор
04.02.2018 15:49Не в этом дело-дело в том, что если не авторизован-можно случайно запросить много раз смс и вызвать flood wait-когда строчка закомментирована-нельзя
SlavikMIPT Автор
04.02.2018 21:54авторизация требуется для одной сессии, а когда отлаживаешь на разных машинах — легко случайно на новой машине запросить несколько раз смс, получив блок на сутки — поэтому так вот обезапасил — на новой машине раскомментировал, залогинился, закомментировал
t0rr
Туториал годный, но если им будут пользоваться новички, то лучше сразу приучать их к прекрасному :)
Можно использовать f-string вместо кучи сложений:
Или через .join:
Можно использовать передачу словаря или json вместо строки и её парсинга.
Можно использовать двух ботов в одном коде, вместо разделения.
Можно использовать таски asyncio вместо запуска cопрограмм.
Можно использовать .get() в словарях, чтобы не ловить Exception в случае недоступности ключа.
SlavikMIPT Автор
спасибо за совет