From 803a6bf9f2e9b9106db8bbe70e5d45eec27220c1 Mon Sep 17 00:00:00 2001 From: protokey Date: Thu, 1 Oct 2026 09:22:05 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D1=81?= =?UTF-8?q?=D0=B2=D0=BE=D0=B5=D0=B3=D0=BE=20Bot=20API=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=B0=20=D0=B1=D0=B5=D0=B7=20TELEGRAM=5FFILE?= =?UTF-8?q?S=5FURL=20=D0=B8=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=20=D0=B2=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Без TELEGRAM_FILES_URL адрес скачивания был голым путем, а текст ошибки с путем (в нем токен бота) уходил в чат. Теперь сайт не стартует без TELEGRAM_FILES_URL при заданном TELEGRAM_API_URL, а в ошибке скачивания только тип исключения. env_example: 127.0.0.1 вместо localhost, чтобы не пробовать ::1. Co-Authored-By: Claude Opus 5.5 --- env_example | 6 +++--- src/Telegram/README.md | 2 +- src/Telegram/api.py | 14 ++++++++++++-- src/Telegram/tests.py | 18 ++++++++++++++++++ 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/env_example b/env_example index cb9a19b..94e03ba 100644 --- a/env_example +++ b/env_example @@ -2,11 +2,11 @@ TG_APP_ID= TG_APP_HASH= # Адрес своего Bot API сервера для Django. Пусто - api.telegram.org -TELEGRAM_API_URL=http://localhost:8081 +TELEGRAM_API_URL=http://127.0.0.1:8081 # Файлы, присланные ботам документами (сервис telegram-files): свой Bot API сервер не отдает их по HTTP -TELEGRAM_FILES_URL=http://localhost:8083 +TELEGRAM_FILES_URL=http://127.0.0.1:8083 # Адрес сайта для webhook ботов, как его видит Bot API сервер (он в сети хоста, network_mode: host) -TELEGRAM_WEBHOOK_URL=http://localhost:8000 +TELEGRAM_WEBHOOK_URL=http://127.0.0.1:8000 # Хосты сайта через запятую, в том числе хост из TELEGRAM_WEBHOOK_URL ALLOWED_HOSTS=127.0.0.1,localhost diff --git a/src/Telegram/README.md b/src/Telegram/README.md index 0d3cd25..af309f4 100644 --- a/src/Telegram/README.md +++ b/src/Telegram/README.md @@ -71,7 +71,7 @@ class Shop(Interface): 3. Отдельный процесс не нужен: обновления принимает сайт. Смена интерфейса применяется без перезапуска. `TELEGRAM_API_URL` направляет бота на свой Bot API сервер (сервис `telegram` в `docker-compose.yaml`). -`TELEGRAM_FILES_URL` - раздача файлов своего Bot API сервера (сервис `telegram-files`). В `--local` режиме сервер не отдает файлы по HTTP, а пишет их в свою папку (`api.WORK_DIR`) с правами 0600 от uid 101. Рядом с ним работает nginx, воркеры которого тоже от uid 101: он отдает только `documents/` и слушает только локальный адрес. +`TELEGRAM_FILES_URL` - раздача файлов своего Bot API сервера (сервис `telegram-files`). В `--local` режиме сервер не отдает файлы по HTTP, а пишет их в свою папку (`api.WORK_DIR`) с правами 0600 от uid 101. Рядом с ним работает nginx, воркеры которого тоже от uid 101: он отдает только `documents/` и слушает только локальный адрес. При заданном `TELEGRAM_API_URL` переменная обязательна: без нее сайт не запустится (`ImproperlyConfigured`). `TELEGRAM_WEBHOOK_URL` - адрес сайта, как его видит Bot API сервер, к нему дописывается путь webhook. Сервер в docker запущен с `--local` (`TELEGRAM_LOCAL`), поэтому webhook может быть на http и на любом порту. Хост из этого адреса нужно добавить в `ALLOWED_HOSTS`. diff --git a/src/Telegram/api.py b/src/Telegram/api.py index 253df8e..479ca1b 100644 --- a/src/Telegram/api.py +++ b/src/Telegram/api.py @@ -1,8 +1,13 @@ +import logging + import requests import telebot from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from telebot import apihelper +logger = logging.getLogger(__name__) + def configure_api_server(): """Направляет pyTelegramBotAPI на свой инстанс Bot API сервера, если он задан в TELEGRAM_API_URL.""" @@ -10,7 +15,10 @@ def configure_api_server(): url = settings.TELEGRAM_API_URL.rstrip('/') apihelper.API_URL = url + '/bot{0}/{1}' # в --local режиме сервер файлы не отдает, их раздает TELEGRAM_FILES_URL по пути внутри WORK_DIR - apihelper.FILE_URL = getattr(settings, 'TELEGRAM_FILES_URL', '').rstrip('/') + '{1}' + files_url = getattr(settings, 'TELEGRAM_FILES_URL', '') + if not files_url: + raise ImproperlyConfigured('TELEGRAM_API_URL is set, so TELEGRAM_FILES_URL is required') + apihelper.FILE_URL = files_url.rstrip('/') + '{1}' CLOUD_API_URL = 'https://api.telegram.org' @@ -62,7 +70,9 @@ def download_file(api: telebot.TeleBot, file_id: str, max_size: int) -> bytes: except ApiError: raise except Exception as error: - raise ApiError(f'download: {error}') from error + # путь к файлу и URL содержат токен бота: текст ошибки уходит в чат, поэтому подробности только в лог + logger.exception('Failed to download file %s', file_id) + raise ApiError(f'download: {type(error).__name__}') from error if len(content) > max_size: raise FileTooLarge(f'file is larger than {max_size} bytes') return content diff --git a/src/Telegram/tests.py b/src/Telegram/tests.py index 8c0e9d2..c6679e0 100644 --- a/src/Telegram/tests.py +++ b/src/Telegram/tests.py @@ -194,6 +194,24 @@ class DocumentTests(TestCase): 'http://127.0.0.1:8083/1:t/documents/file_0.zip', ) + @override_settings(TELEGRAM_API_URL='http://127.0.0.1:8081', TELEGRAM_FILES_URL='') + def test_own_server_requires_file_url(self): + from django.core.exceptions import ImproperlyConfigured + from telebot import apihelper + + with mock.patch.object(apihelper, 'API_URL', None), mock.patch.object(apihelper, 'FILE_URL', None): + with self.assertRaises(ImproperlyConfigured): + bot_api.configure_api_server() + + @override_settings(TELEGRAM_API_URL='http://127.0.0.1:8081') + def test_download_error_hides_token(self): + api = mock.Mock() + api.get_file.return_value = SimpleNamespace(file_path=f'{bot_api.WORK_DIR}/1:secret/documents/a.zip', file_size=1) + api.download_file.side_effect = ValueError('No connection adapters were found for /1:secret/documents/a.zip') + with self.assertLogs('Telegram.api', 'ERROR'), self.assertRaises(bot_api.ApiError) as raised: + bot_api.download_file(api, 'id', 10) + self.assertNotIn('secret', str(raised.exception)) + @override_settings(TELEGRAM_WEBHOOK_URL='http://localhost:8000/') class PublishTests(TestCase):