refactor: review fixes and code improvements

- Add shared proxy module (bot/utils/proxy.py) to eliminate duplicate SOCKS5 parsing
- Fix AI client: escape HTML before Markdown→HTML conversion, unify timeouts,
  make health check optional and disabled by default, handle 429 retries
- Fix ai.py: correct forwarded message handling (aiogram 3.x forward_origin),
  pass relevant summaries via extra_system_content
- Fix dialogue.py: only respond to Astra's messages, use system context instead
  of prompt injection, answer on the limit message before phase transition
- Fix voice.py: load Whisper model in thread pool, safe WAV path generation
- Improve database.py: composite indexes, Boolean is_active, upsert file_id cache,
  add context cleanup helper
- Update weather.py and yadisk_download.py to use shared proxy connector
- Update yadisk.py: validate URL before cache clear, add download size limit,
  wrap sync file ops in to_thread
- Reuse S3 client via lru_cache
- Update setup_commands with /aiclear and /aiuser
- Update README, Dockerfile (Python 3.11), docker-compose (mount models)
- Pin dependency versions, remove unused httpx[socks]
- Add basic pytest tests for layout converter and voice normalization
This commit is contained in:
Галингер Р.С.
2026-07-07 18:42:10 +07:00
parent 76e5701eba
commit 0f674f8832
23 changed files with 894 additions and 578 deletions
+16 -38
View File
@@ -2,10 +2,9 @@ import logging
from datetime import datetime
import aiohttp
from aiohttp_socks import ProxyConnector, ProxyType
from config import API_WEATHER, PROXY_ENABLED, PROXY_URL
from bot.bot import bot
from bot.utils.proxy import get_proxy_connector
from config import API_WEATHER
logger = logging.getLogger(__name__)
@@ -28,47 +27,26 @@ def get_wind_direction(deg: float) -> str:
return "Северо-запад"
def _get_connector():
if PROXY_ENABLED and PROXY_URL:
parsed = PROXY_URL.replace("socks5://", "").replace("socks5h://", "")
if "@" in parsed:
auth, host_port = parsed.split("@", 1)
username, password = auth.split(":", 1)
else:
username = None
password = None
host_port = parsed
host, port = host_port.rsplit(":", 1)
port = int(port)
return ProxyConnector(
proxy_type=ProxyType.SOCKS5,
host=host,
port=port,
username=username,
password=password,
)
return None
async def get_weather(message) -> None:
if len(message.text.split(maxsplit=1)) == 1:
await bot.send_message(message.chat.id, "Пожалуйста, укажите город.")
text_parts = message.text.split(maxsplit=1)
if len(text_parts) == 1:
await message.answer("Пожалуйста, укажите город.")
return
city = message.text.split(maxsplit=1)[1].strip()
city = text_parts[1].strip()
url = "https://api.openweathermap.org/data/2.5/weather"
params = {"q": city, "appid": API_WEATHER, "units": "metric", "lang": "ru"}
timeout = aiohttp.ClientTimeout(total=30, sock_connect=15, sock_read=15)
connector = _get_connector()
connector = get_proxy_connector()
try:
async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
async with aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=30, sock_connect=15, sock_read=15),
connector=connector,
) as session:
async with session.get(url, params=params) as response:
if response.status == 404:
await bot.send_message(message.chat.id, "Город не найден. Пожалуйста, уточните запрос.")
await message.answer("Город не найден. Пожалуйста, уточните запрос.")
return
response.raise_for_status()
weather_data = await response.json()
@@ -103,16 +81,16 @@ async def get_weather(message) -> None:
f"Видимость: {visibility} м\n"
f"Восход: {sunrise_time}, закат: {sunset_time}"
)
await bot.send_message(message.chat.id, weather_message, parse_mode=None)
await message.answer(weather_message, parse_mode=None)
except aiohttp.ClientResponseError as exc:
logger.error("HTTP ошибка погоды: %s", exc)
await bot.send_message(message.chat.id, "При получении данных произошла ошибка, попробуйте еще раз.")
await message.answer("При получении данных произошла ошибка, попробуйте еще раз.")
except aiohttp.ClientError as exc:
logger.error("Ошибка запроса погоды: %s", exc)
await bot.send_message(message.chat.id, "Не удалось связаться с погодным сервисом.")
await message.answer("Не удалось связаться с погодным сервисом.")
except Exception as exc:
logger.exception("Неожиданная ошибка погоды: %s", exc)
await bot.send_message(message.chat.id, "Произошла ошибка при обработке погоды.")
await message.answer("Произошла ошибка при обработке погоды.")
finally:
if connector:
await connector.close()