Files

283 lines
11 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import asyncio
import logging
import os
import re
import time
from datetime import datetime
from urllib.parse import urljoin
import aiohttp
import boto3
from aiogram.exceptions import TelegramBadRequest, TelegramRetryAfter
from aiogram.types import FSInputFile
from config import ACCESS_KEY, API_WEATHER, BUCKET_NAME, SECRET_KEY, bot
from database import get_file_id, get_user_data, save_file_id
from download_files import download_yandex_files
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("functions")
async def send_photo_with_caption(chat_id: int, file_key: str, file_path: str, caption: str) -> None:
file_id = get_file_id(file_key)
if file_id:
try:
await bot.send_photo(chat_id, file_id, caption=caption)
return
except Exception as exc:
logger.error("Ошибка при отправке файла по ID: %s", exc)
try:
sent_message = await bot.send_photo(chat_id, FSInputFile(file_path), caption=caption)
save_file_id(file_key, sent_message.photo[-1].file_id)
except Exception as exc:
logger.error("Ошибка при загрузке файла: %s", exc)
async def info_ban_status(message) -> None:
user_id = message.from_user.id
chat_id = message.chat.id
user_info = get_user_data(user_id, chat_id)
if not user_info or not bool(user_info[4]):
await send_photo_with_caption(chat_id, "clear_robot", "img/clear_robot.jpg", "Не переживай, ты чист и невинен!")
return
username = user_info[6] or message.from_user.full_name
ban_until = user_info[5] + 300
ban_until_txt = time.strftime("%d %b %Y %H:%M:%S", time.localtime(ban_until))
await send_photo_with_caption(
chat_id,
"time_robot",
"img/time_robot.jpg",
f"{username}\nТвой бан на стикеры и гифки будет снят {ban_until_txt}",
)
def escape_markdown(text: str) -> str:
return re.sub(r"([_\*\[\]\(\)~`>#\+\-=\|{}\.!])", r"\\\1", text)
async def edit_message_with_retry(message, text: str, **kwargs):
while True:
try:
return await message.edit_text(text, **kwargs)
except TelegramRetryAfter as exc:
logger.warning("Превышен лимит на редактирование сообщений. Жду %s сек.", exc.retry_after)
await asyncio.sleep(exc.retry_after)
except TelegramBadRequest as exc:
logger.error("Ошибка редактирования сообщения: %s", exc)
return None
async def send_message_with_retry(message, text: str, **kwargs) -> None:
while True:
try:
await message.answer(text, **kwargs)
return
except TelegramRetryAfter as exc:
logger.warning("Превышен лимит на отправку сообщений. Жду %s сек.", exc.retry_after)
await asyncio.sleep(exc.retry_after)
def get_wind_direction(deg: float) -> str:
if deg >= 337.5 or deg < 22.5:
return "Север"
if deg < 67.5:
return "Северо-восток"
if deg < 112.5:
return "Восток"
if deg < 157.5:
return "Юго-восток"
if deg < 202.5:
return "Юг"
if deg < 247.5:
return "Юго-запад"
if deg < 292.5:
return "Запад"
return "Северо-запад"
async def get_weather(message) -> None:
if len(message.text.split(maxsplit=1)) == 1:
await bot.send_message(message.chat.id, "Пожалуйста, укажите город.")
return
city = message.text.split(maxsplit=1)[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)
try:
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url, params=params) as response:
if response.status == 404:
await bot.send_message(message.chat.id, "Город не найден. Пожалуйста, уточните запрос.")
return
response.raise_for_status()
weather_data = await response.json()
weather_description = weather_data["weather"][0]["description"]
temperature = weather_data["main"]["temp"]
feels_like = weather_data["main"]["feels_like"]
temp_min = weather_data["main"]["temp_min"]
temp_max = weather_data["main"]["temp_max"]
humidity = weather_data["main"]["humidity"]
pressure = int(weather_data["main"]["pressure"] / 1.333)
wind_speed = weather_data["wind"]["speed"]
wind_deg = weather_data["wind"].get("deg", 0)
wind_direction = get_wind_direction(wind_deg)
rain_1h = weather_data.get("rain", {}).get("1h", 0)
clouds_all = weather_data["clouds"]["all"]
visibility = weather_data.get("visibility", 0)
sunrise_time = datetime.fromtimestamp(weather_data["sys"]["sunrise"]).strftime("%H:%M")
sunset_time = datetime.fromtimestamp(weather_data["sys"]["sunset"]).strftime("%H:%M")
weather_message = (
f"🌆 Погода в городе {city}:\n\n"
f"☁️ Описание: {weather_description}\n"
f"🌡️ Температура: {temperature}°C (ощущается как {feels_like}°C)\n"
f"📉 Минимальная температура: {temp_min}°C\n"
f"📈 Максимальная температура: {temp_max}°C\n"
f"💧 Влажность: {humidity}%\n"
f"🌬️ Давление: {pressure} мм рт.ст\n"
f"💨 Скорость ветра: {wind_speed} м/с, направление: {wind_direction}\n"
f"🌧️ Осадки за последний час: {rain_1h} мм\n"
f"☁️ Облачность: {clouds_all}%\n"
f"👁️ Видимость: {visibility} м\n"
f"🌅 Восход: {sunrise_time}, закат: {sunset_time}"
)
await bot.send_message(message.chat.id, weather_message)
except aiohttp.ClientResponseError as exc:
logger.error("HTTP ошибка погоды: %s", exc)
await bot.send_message(message.chat.id, "При получении данных произошла ошибка, попробуйте еще раз.")
except aiohttp.ClientError as exc:
logger.error("Ошибка запроса погоды: %s", exc)
await bot.send_message(message.chat.id, "Не удалось связаться с погодным сервисом.")
except Exception as exc:
logger.exception("Неожиданная ошибка погоды: %s", exc)
await bot.send_message(message.chat.id, "Произошла ошибка при обработке погоды.")
def _build_s3_client():
endpoint_url = "https://storage.yandexcloud.net"
return boto3.client(
"s3",
endpoint_url=endpoint_url,
aws_access_key_id=ACCESS_KEY,
aws_secret_access_key=SECRET_KEY,
)
def is_valid_yandex_public_link(value: str) -> bool:
value = value.strip().lower()
return (
value.startswith("https://disk.yandex.")
or value.startswith("http://disk.yandex.")
or value.startswith("https://yadi.sk/")
or value.startswith("http://yadi.sk/")
)
async def download_yd(message) -> None:
text = (message.text or "").strip()
parts = text.split(maxsplit=1)
command_part = parts[0] if parts else ""
argument = parts[1].strip() if len(parts) > 1 else ""
command_name = command_part.split("@", 1)[0].lower()
if command_name != "/ydf":
await message.answer("Некорректная команда.")
return
if not argument:
await message.answer("Не указана ссылка. Используйте: /ydf [ссылка]")
return
files_down = argument
if files_down.startswith("(new)"):
files_down = files_down.replace("(new)", "", 1).strip()
if not files_down:
await message.answer("После (new) нужно указать ссылку: /ydf (new) [ссылка]")
return
save_file_id(files_down, None)
if not is_valid_yandex_public_link(files_down):
await message.answer("Похоже, это не публичная ссылка Яндекс.Диска.")
return
file_id = get_file_id(files_down)
if file_id:
await message.answer_document(
file_id,
caption="Файл отправлен из кэша Telegram. Для повторной загрузки: /ydf (new) [ссылка]",
)
return
initial_message = await message.answer("Найдена ссылка, начинаю загрузку")
last_message_text = "Найдена ссылка, начинаю загрузку"
async def progress_callback(downloaded_size: int, total_size: int) -> None:
nonlocal last_message_text
if total_size <= 0:
return
percent = int(downloaded_size / total_size * 100)
new_message_text = f"Загрузка файла: {percent}%"
if new_message_text != last_message_text and percent % 5 == 0:
await edit_message_with_retry(initial_message, new_message_text)
last_message_text = new_message_text
try:
file_name = await download_yandex_files(files_down, progress_callback=progress_callback)
except aiohttp.ClientResponseError as exc:
if exc.status == 404:
await initial_message.edit_text("Ссылка не найдена или недоступна для скачивания.")
else:
await initial_message.edit_text(f"Ошибка загрузки: HTTP {exc.status}")
return
except aiohttp.ClientError:
await initial_message.edit_text("Ошибка сети при обращении к Яндекс.Диску.")
return
except Exception:
logger.exception("Ошибка при скачивании файла")
await initial_message.edit_text("Произошла ошибка при скачивании файла.")
return
try:
if os.path.getsize(file_name) < 50 * 1024 * 1024:
document = FSInputFile(file_name)
response = await message.answer_document(document)
save_file_id(files_down, response.document.file_id)
else:
s3 = await asyncio.to_thread(_build_s3_client)
key_name = os.path.basename(file_name)
await asyncio.to_thread(s3.upload_file, file_name, BUCKET_NAME, key_name)
endpoint_url = "https://storage.yandexcloud.net"
file_url = urljoin(endpoint_url, f"/{BUCKET_NAME}/{key_name}")
escaped_file_name = escape_markdown(key_name)
escaped_file_url = escape_markdown(file_url)
await send_message_with_retry(
message,
f"Ваш файл доступен для скачивания [{escaped_file_name}]({escaped_file_url})",
parse_mode="MarkdownV2",
)
finally:
try:
os.remove(file_name)
except OSError:
pass
try:
await initial_message.delete()
except TelegramBadRequest:
pass