Initial commit: MIB bot
This commit is contained in:
Executable
+282
@@ -0,0 +1,282 @@
|
||||
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
|
||||
Reference in New Issue
Block a user