Skip to content

Запускаем Telegram Bot на локальном сервере

При разработке Telegram ботов многие сталкиваются с ограничениями официального Bot API: лимиты на размер файлов, ограниченные возможности вебхуков и другие технические барьеры. В этом руководстве я покажу, как развернуть собственный сервер Telegram Bot API, который даст вам полный контроль над работой бота и избавит от этих ограничений.

Преимущества локального Bot API сервера

Запуск собственного сервера Telegram Bot API дает несколько ключевых преимуществ:

  • Загрузка файлов до 2000 МБ (вместо стандартных 50 МБ)
  • Использование локальных путей для файлов через URI схему
  • Гибкая настройка вебхуков:
    • Возможность использовать HTTP (не только HTTPS)
    • Любые локальные IP-адреса и порты
    • До 100000 соединений (max_webhook_connections)
  • Прямой доступ к файлам через локальные пути (без дополнительной загрузки через getFile)

Установка и настройка Telegram Bot API сервера

Исходный код доступен на GitHub: https://github.com/tdlib/telegram-bot-api

Инструкции по сборке https://tdlib.github.io/telegram-bot-api/build.html?os=Linux

clang:

apt-get update
apt-get upgrade
apt-get install make git zlib1g-dev libssl-dev gperf cmake clang-18 libc++-18-dev libc++abi-18-dev
git clone --recursive https://github.com/tdlib/telegram-bot-api.git
cd telegram-bot-api
rm -rf build
mkdir build
cd build
CXXFLAGS="-stdlib=libc++" CC=/usr/bin/clang-18 CXX=/usr/bin/clang++-18 cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr/local ..
cmake --build . --target install
cd ../..
ls -l /usr/local/bin/telegram-bot-api*

После сборки проверяем работу бинарного файла:

telegram-bot-api --help

Регистрация Telegram приложения

Для работы API сервера необходимо зарегистрировать приложение в Telegram:

  • Перейдите на my.telegram.org
  • Войдите с вашим номером телефона (потребуется код подтверждения)
  • В разделе "API development tools" заполните форму:
    • Укажите название и короткое имя приложения
    • Сохраните полученные api_id и api_hash

Регистрация приложения:

Запуск сервера

Запустим Telegram API сервер в локальном режиме.

Запускаем сервер с нашими параметрами:

telegram-bot-api --local --api-id=YOUR_API_ID --api-hash=YOUR_API_HASH --http-port=8081

По умолчанию сервер слушает на всех интерфейсах (0.0.0.0:8081).

Настройка автозапуска (Systemd)

Создаем файл сервиса/etc/systemd/system/telegram-bot-api.service:

[Unit]
Description=Telegram Bot API Server
After=network.target network-online.target nss-lookup.target

[Service]
Type=simple
ExecStart=/usr/local/bin/telegram-bot-api --local --api-id=<api_id> --api-hash=<api_hash> --http-port=8081
Restart=always
TimeoutStartSec=10
TimeoutStopSec=10

[Install]
WantedBy=multi-user.target

Активируем сервис:

systemctl start telegram-bot-api
systemctl enable telegram-bot-api
systemctl status telegram-bot-api

Создание и настройка бота

Создадим бота:

  • Найдите в Telegram @BotFather
  • Отправьте команду /newbot и следуйте инструкциям
  • Сохраните полученный токен бота (формат: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11)

Отвязываем бота от облачного сервера

curl https://api.telegram.org/bot<BOT_TOKEN>/logOut

Получаем chat ID:

curl https://api.telegram.org/bot<BOT_TOKEN>/getUpdates | jq 'result[].message.chat.id'

Установка и запуск бота

Устанавливаем зависимости:

pip install python-telegram-bot yt-dlp

Если возникает ошибка externally-managed-environment, удалите файл:

rm /usr/lib/python3.12/EXTERNALLY-MANAGED

Создаем исполняемый файл бота (/usr/local/bin/your_bot.py):

#!/usr/bin/env python3

# Telegram Bot for downloading YouTube audio.

# Requirements:
## Telegram Bot API Server: https://github.com/tdlib/telegram-bot-api/
## System packages: ffmpeg
## Python packages: python-telegram-bot, yt-dlp

import logging
import os
from pathlib import Path
from typing import Optional

import yt_dlp
from telegram import Update, InputFile
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, Application

# Configure logging
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)
logger = logging.getLogger(__name__)

# List of allowed user IDs (replace with your actual allowed users)
ALLOWED_USERS = {
    123807789,  # Example user ID 1
    000000000   # Example user ID 2
}
BOT_TOKEN = "token"
FFMPEG_PATH = "/usr/bin/ffmpeg"  # Default ffmpeg path on Ubuntu

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Send a welcome message when the command /start is issued."""
    user = update.effective_user
    user_id = user.id if user else None

    try:
        await update.message.reply_html(
            rf"Hi {user.mention_html()}! Your user ID is: {user_id}. I'm a bot running in {'local' if LOCAL_MODE else 'normal'} mode.  \n"
            "Available commands:\n"
            "/start - Show this message\n"
            "/help - Show help information\n"
            "/downloadyoutubeaudio url - Download audio from YouTube"
        )
    except Exception as e:
        logger.error(f"Cannot send start message: {e}")
        await update.message.reply_text(f"Sorry, I couldn't send the welcome message: {e}")

async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Show help information."""
    await update.message.reply_text(
        "Available commands:\n"
        "/start - Show welcome message\n"
        "/help - Show this help\n"
        "/downloadyoutubeaudio <url> - Download audio from YouTube\n\n"
        "Example:\n"
        "/downloadyoutubeaudio https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    )

async def downloadyoutubeaudio(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user = update.effective_user
    # Check if user is allowed
    if not user or user.id not in ALLOWED_USERS:
        await update.message.reply_text(
            chat_id=update.effective_chat.id,
            text="?? Sorry, you don't have permission to use this command."
        )
        return

    url = update.message.text.split()[1] if update.message.text else None  # Assuming the URL is sent as a message
    # Validate URL
    if 'youtube.com' not in url and 'youtu.be' not in url:
        await update.message.reply_text("Please provide a valid YouTube URL.")
        return

    # Set up yt-dlp options
    ydl_opts = {
        'format': 'bestaudio/best',
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'mp3',
            'preferredquality': '0',  # Best quality
        }],
        'ffmpeg_location': '/usr/bin/ffmpeg',  # Default ffmpeg path on Ubuntu
        'outtmpl': '%(title)s.%(ext)s',
        'download_archive': None,  # Don't record downloaded videos
        'external_downloader_args': {
            'ffmpeg_i': ['-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5']
        }
    }

    try:
        # Send "processing" message
        processing_msg = await update.message.reply_text("Downloading audio...")

        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            info = ydl.extract_info(url, download=True)
            filename = ydl.prepare_filename(info)
            # The actual file will have the mp3 extension
            mp3_filename = os.path.splitext(filename)[0] + '.mp3'

            if mp3_filename:
                # Send the audio file
                with open(mp3_filename, 'rb') as audio_file:
                    await update.message.reply_audio(
                        audio=audio_file,
                        title=info.get('title', 'audio'),
                        performer=info.get('uploader', 'unknown artist'),
                        duration=info.get('duration', 0)
                    )

                # Delete the temporary file
                os.remove(mp3_filename)

                # Update processing message
                await processing_msg.edit_text("Audio sent successfully!")

    except Exception as e:
        logger.error(f"Error downloading video: {e}")
        await update.message.reply_text(f"Sorry, I couldn't download that video. Error: {e}")

        # If processing message exists, update it with error
        if 'processing_msg' in locals():
            await processing_msg.edit_text(f"Sorry, I couldn't download that video. Error: {e}"
            )

if __name__ == '__main__':
    """Start the bot."""
    application = ApplicationBuilder().token(BOT_TOKEN).base_url("http://localhost:8081/bot").build()

    # Register command handlers
    application.add_handler(CommandHandler('start', start))
    application.add_handler(CommandHandler("help", help_command))
    application.add_handler(CommandHandler("downloadyoutubeaudio", downloadyoutubeaudio))

    logger.info("Bot is starting...")
    application.run_polling()

Код доступен в репозитории

Делаем файл исполняемым:

chmod +x your_bot.py

Запускаем бота:

./your_bot.py

Устанавливаем пакет для конвертации:

apt install ffmpeg

Настройка автозапуска бота (Systemd)

Создаем файл /etc/systemd/system/telegram-bot.service:

[Unit]
Description=Telegram Bot
After=network.target network-online.target nss-lookup.target

[Service]
Type=simple
ExecStart=/usr/local/bin/your_bot.py
Restart=always
TimeoutStartSec=10
TimeoutStopSec=10

[Install]
WantedBy=multi-user.target

Активируем сервис:

systemctl start telegram-bot
systemctl enable telegram-bot
systemctl status telegram-bot

Немного про сетевую связность

Важно отметить, что:

  • Бот подключается только к локальному Bot API серверу (порт 8081)
  • Bot API сервер взаимодействует с серверами Telegram
  • Рекомендуется ограничить внешний доступ к порту 8081 через фаерволл

Итоги

В результате мы: - Развернули локальный Bot API сервер как systemd-сервис - Зарегистрировали приложение в Telegram - Создали и настроили бота через @BotFather - Запустили бота как systemd-сервис

Источники