It's version 0.4
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
def register(dp, state, bot):
|
||||
from . import handlers
|
||||
|
||||
handlers.register_handlers(dp, state, bot)
|
||||
|
||||
|
||||
def unregister(dp):
|
||||
# Здесь можно удалить хендлеры, если нужно
|
||||
dp.message_handlers.handlers.clear()
|
||||
|
||||
@@ -12,8 +12,8 @@ import uuid
|
||||
from config import Config
|
||||
|
||||
# Настройка кодировки для всего приложения
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,13 +24,15 @@ LIMIT = 2 * 1024 * 1024 * 1024 # 2 ГБ
|
||||
async def safe_filename(name: str) -> str:
|
||||
"""Создает безопасное имя файла"""
|
||||
# Нормализуем Unicode символы
|
||||
normalized = unicodedata.normalize('NFKD', name)
|
||||
normalized = unicodedata.normalize("NFKD", name)
|
||||
# Убираем акценты и специальные символы, оставляем только ASCII
|
||||
ascii_name = normalized.encode('ascii', 'ignore').decode('ascii')
|
||||
ascii_name = normalized.encode("ascii", "ignore").decode("ascii")
|
||||
# Заменяем проблемные символы
|
||||
safe_name = "".join(c if c.isalnum() or c in (' ', '-', '_', '.') else '_' for c in ascii_name)
|
||||
safe_name = "".join(
|
||||
c if c.isalnum() or c in (" ", "-", "_", ".") else "_" for c in ascii_name
|
||||
)
|
||||
# Убираем множественные подчеркивания и обрезаем длину
|
||||
safe_name = '_'.join(filter(None, safe_name.split('_')))
|
||||
safe_name = "_".join(filter(None, safe_name.split("_")))
|
||||
return safe_name[:100] or f"video_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
|
||||
@@ -38,21 +40,23 @@ async def get_video_info(url: str) -> dict:
|
||||
"""Получает информацию о видео через yt-dlp"""
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
'yt-dlp',
|
||||
'--dump-json',
|
||||
'--no-playlist',
|
||||
"yt-dlp",
|
||||
"--dump-json",
|
||||
"--no-playlist",
|
||||
url,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await process.communicate()
|
||||
|
||||
if process.returncode == 0:
|
||||
result = json.loads(stdout.decode('utf-8', errors='ignore'))
|
||||
logger.info(f"Информация о видео получена: {result.get('title', 'Unknown')}")
|
||||
result = json.loads(stdout.decode("utf-8", errors="ignore"))
|
||||
logger.info(
|
||||
f"Информация о видео получена: {result.get('title', 'Unknown')}"
|
||||
)
|
||||
return result
|
||||
else:
|
||||
error_msg = stderr.decode('utf-8', errors='ignore')
|
||||
error_msg = stderr.decode("utf-8", errors="ignore")
|
||||
logger.warning(f"yt-dlp ошибка: {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
@@ -75,38 +79,54 @@ async def download_mp4_to_dropbox(url: str) -> tuple[str, dict]:
|
||||
duration = 0
|
||||
|
||||
if video_info:
|
||||
title = await safe_filename(video_info.get('title', 'Unknown_Title'))
|
||||
uploader = await safe_filename(video_info.get('uploader', 'Unknown_Uploader'))
|
||||
duration = video_info.get('duration', 0)
|
||||
title = await safe_filename(video_info.get("title", "Unknown_Title"))
|
||||
uploader = await safe_filename(
|
||||
video_info.get("uploader", "Unknown_Uploader")
|
||||
)
|
||||
duration = video_info.get("duration", 0)
|
||||
logger.info(f"Обработано видео: {title}")
|
||||
|
||||
# ОПТИМИЗИРОВАННЫЕ НАСТРОЙКИ ДЛЯ СКОРОСТИ
|
||||
download_process = await asyncio.create_subprocess_exec(
|
||||
'yt-dlp',
|
||||
'-f', 'bestvideo[height<=720][filesize<800M]+bestaudio/best[height<=720][filesize<800M]',
|
||||
'--no-playlist',
|
||||
'-o', output_template,
|
||||
'--ignore-errors',
|
||||
'--no-warnings',
|
||||
'--format-sort', 'quality,res:720,size:800M',
|
||||
'--concurrent-fragments', '4',
|
||||
"yt-dlp",
|
||||
"-f",
|
||||
"bestvideo[height<=720][filesize<800M]+bestaudio/best[height<=720][filesize<800M]",
|
||||
"--no-playlist",
|
||||
"-o",
|
||||
output_template,
|
||||
"--ignore-errors",
|
||||
"--no-warnings",
|
||||
"--format-sort",
|
||||
"quality,res:720,size:800M",
|
||||
"--concurrent-fragments",
|
||||
"4",
|
||||
url,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
stdout, stderr = await asyncio.wait_for(download_process.communicate(), timeout=600) # Уменьшил таймаут
|
||||
stdout, stderr = await asyncio.wait_for(
|
||||
download_process.communicate(), timeout=600
|
||||
) # Уменьшил таймаут
|
||||
|
||||
# Остальной код без изменений...
|
||||
if download_process.returncode != 0:
|
||||
error_msg = stderr.decode('utf-8', errors='ignore') if stderr else "Unknown error"
|
||||
error_msg = (
|
||||
stderr.decode("utf-8", errors="ignore")
|
||||
if stderr
|
||||
else "Unknown error"
|
||||
)
|
||||
logger.error(f"Ошибка скачивания: {error_msg}")
|
||||
raise Exception(f"Ошибка скачивания: {error_msg}")
|
||||
|
||||
mp4_files = glob.glob(os.path.join(temp_dir, "*.mp4"))
|
||||
if not mp4_files:
|
||||
video_files = glob.glob(os.path.join(temp_dir, "*.*"))
|
||||
video_files = [f for f in video_files if f.lower().endswith(('.mp4', '.mkv', '.avi', '.mov', '.webm'))]
|
||||
video_files = [
|
||||
f
|
||||
for f in video_files
|
||||
if f.lower().endswith((".mp4", ".mkv", ".avi", ".mov", ".webm"))
|
||||
]
|
||||
if video_files:
|
||||
mp4_files = [video_files[0]]
|
||||
|
||||
@@ -123,25 +143,27 @@ async def download_mp4_to_dropbox(url: str) -> tuple[str, dict]:
|
||||
dbx = dropbox.Dropbox(DROPBOX_TOKEN)
|
||||
dropbox_path = f"/{final_filename}"
|
||||
|
||||
logger.info(f"Загружаем файл в Dropbox: {dropbox_path} (размер: {size / (1024 * 1024):.1f} MB)")
|
||||
logger.info(
|
||||
f"Загружаем файл в Dropbox: {dropbox_path} (размер: {size / (1024 * 1024):.1f} MB)"
|
||||
)
|
||||
|
||||
with open(actual_file, "rb") as f:
|
||||
file_data = f.read()
|
||||
dbx.files_upload(
|
||||
file_data,
|
||||
dropbox_path,
|
||||
mode=dropbox.files.WriteMode("overwrite")
|
||||
mode=dropbox.files.WriteMode("overwrite"),
|
||||
)
|
||||
|
||||
shared_link = dbx.sharing_create_shared_link_with_settings(dropbox_path)
|
||||
link = shared_link.url.replace("?dl=0", "?dl=1")
|
||||
|
||||
metadata = {
|
||||
'title': title,
|
||||
'uploader': uploader,
|
||||
'duration': duration,
|
||||
'filesize': size,
|
||||
'quality': 'optimized for speed'
|
||||
"title": title,
|
||||
"uploader": uploader,
|
||||
"duration": duration,
|
||||
"filesize": size,
|
||||
"quality": "optimized for speed",
|
||||
}
|
||||
|
||||
logger.info(f"Успешно загружено в Dropbox: {link}")
|
||||
@@ -154,5 +176,3 @@ async def download_mp4_to_dropbox(url: str) -> tuple[str, dict]:
|
||||
except Exception as e:
|
||||
logger.error(f"Общая ошибка: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
|
||||
@@ -2,18 +2,16 @@ import logging
|
||||
from aiogram import Dispatcher, Bot
|
||||
from aiogram.filters import Command
|
||||
from models.state import BotState
|
||||
from utils.antispam import admin_required
|
||||
|
||||
from .dowmp4 import download_mp4_to_dropbox
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
@dp.message(Command("dowmp4"))
|
||||
#@admin_required(4)
|
||||
# @admin_required(4)
|
||||
async def dowmp4_handler(message):
|
||||
"""Обработчик команды /dowmp4"""
|
||||
try:
|
||||
@@ -22,7 +20,9 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
await message.answer("Пожалуйста, укажите URL видео после команды /dowmp4")
|
||||
return
|
||||
|
||||
processing_msg = await message.answer("⏳ Начинаю обработку видео... Это может занять несколько минут.")
|
||||
processing_msg = await message.answer(
|
||||
"⏳ Начинаю обработку видео... Это может занять несколько минут."
|
||||
)
|
||||
|
||||
try:
|
||||
# Скачиваем и загружаем в Dropbox
|
||||
@@ -44,12 +44,13 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
await message.answer(
|
||||
f"✅ **Видео успешно обработано!**\n\n{caption}",
|
||||
parse_mode="Markdown",
|
||||
disable_web_page_preview=True
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
await message.answer(f"❌ Ошибка: {str(e)}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке /dowmp4: {e}", exc_info=True)
|
||||
await message.answer("❌ Произошла ошибка при обработке видео. Попробуйте позже.")
|
||||
|
||||
await message.answer(
|
||||
"❌ Произошла ошибка при обработке видео. Попробуйте позже."
|
||||
)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
def register(dp, state, bot):
|
||||
from . import handlers
|
||||
|
||||
handlers.register_handlers(dp, state, bot)
|
||||
|
||||
|
||||
def unregister(dp):
|
||||
# Здесь можно удалить хендлеры, если нужно
|
||||
dp.message_handlers.handlers.clear()
|
||||
@@ -0,0 +1,190 @@
|
||||
import logging
|
||||
import base64
|
||||
from io import BytesIO
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
from aiogram import Dispatcher, Bot
|
||||
from aiogram.types import Message, FSInputFile, BufferedInputFile
|
||||
from aiogram.filters import Command
|
||||
|
||||
from models.state import BotState
|
||||
from config import Config
|
||||
|
||||
from storage.message_storage import save_message
|
||||
|
||||
from transformers import pipeline
|
||||
|
||||
from utils.antispam import saving
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SD_URL = "http://192.168.31.95:7860/sdapi/v1/txt2img"
|
||||
|
||||
# Загружаем пайплайн перевода один раз при старте (синхронный)
|
||||
translator = pipeline("translation", model="Helsinki-NLP/opus-mt-ru-en")
|
||||
|
||||
|
||||
async def translate_to_en(text: str) -> str:
|
||||
try:
|
||||
# выполняем перевод в отдельном потоке, чтобы не блокировать event loop
|
||||
result = await asyncio.to_thread(translator, text, max_length=512)
|
||||
return result[0]["translation_text"]
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка перевода: {e}")
|
||||
return text
|
||||
|
||||
|
||||
async def generate_img2img(prompt: str, init_image: BytesIO) -> BytesIO | None:
|
||||
"""
|
||||
Генерация изображения по методу img2img.
|
||||
:param prompt: текстовый промт (уже переведённый на английский)
|
||||
:param init_image: входное изображение в BytesIO
|
||||
:return: BytesIO с результатом или None при ошибке
|
||||
"""
|
||||
try:
|
||||
# кодируем входное изображение в base64
|
||||
init_image_base64 = base64.b64encode(init_image.getvalue()).decode("utf-8")
|
||||
|
||||
payload = {
|
||||
"init_images": [init_image_base64],
|
||||
"prompt": prompt,
|
||||
"negative_prompt": "blurry, low quality, bad anatomy, watermark, text, cropped",
|
||||
"steps": 20, # можно 15–20
|
||||
"width": 1024, # лучше подставлять размеры исходного фото
|
||||
"height": 1024,
|
||||
"sampler_name": "Euler a", # мягкий и стабильный для img2img
|
||||
"Schedule_type": "Karras",
|
||||
"cfg_scale": 6, # чуть ниже, чем для txt2img
|
||||
"seed": -1,
|
||||
"denoising_strength": 0.8, # 0.3–0.5 для «сохранить стиль», 0.6–0.8 для «перерисовать»
|
||||
"restore_faces": True, # если работаешь с людьми
|
||||
"override_settings": {
|
||||
"sd_model_checkpoint": "waiNSFWIllustrious_v150.safetensors"
|
||||
},
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
SD_URL.replace("txt2img", "img2img"), json=payload
|
||||
) as resp:
|
||||
if resp.status != 200:
|
||||
logger.error(f"Stable Diffusion img2img API error: {resp.status}")
|
||||
return None
|
||||
r = await resp.json()
|
||||
image_base64 = r["images"][0]
|
||||
return BytesIO(base64.b64decode(image_base64))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка img2img: {e}")
|
||||
return None
|
||||
|
||||
|
||||
# sd_xl_base_1.0.safetensors
|
||||
# waiNSFWIllustrious_v150.safetensors
|
||||
async def generate_image(prompt: str) -> BytesIO | None:
|
||||
payload = {
|
||||
"prompt": prompt,
|
||||
"negative_prompt": "blurry, low quality, bad anatomy, watermark, text, cropped",
|
||||
"steps": 20,
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"sampler_name": "Euler a", # сэмплер
|
||||
"cfg_scale": 7, # насколько строго следовать промту
|
||||
"seed": -1, # -1 = случайный сид
|
||||
"batch_size": 1, # сколько картинок за раз
|
||||
"n_iter": 1, # сколько раз повторить генерацию
|
||||
"restore_faces": False, # восстановление лиц
|
||||
"tiling": False, # тайлинг для текстур
|
||||
"enable_hr": False, # highres fix (двухэтапная генерация)
|
||||
"denoising_strength": 0.7, # сила денойзинга (актуально при enable_hr или img2img)
|
||||
"hr_scale": 2, # во сколько раз увеличить при highres fix
|
||||
"hr_upscaler": "Latent", # апскейлер для highres fix
|
||||
"override_settings": {
|
||||
"sd_model_checkpoint": "waiNSFWIllustrious_v150.safetensors" # выбор модели
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(SD_URL, json=payload) as resp:
|
||||
if resp.status != 200:
|
||||
logger.error(f"Stable Diffusion API error: {resp.status}")
|
||||
return None
|
||||
r = await resp.json()
|
||||
image_base64 = r["images"][0]
|
||||
return BytesIO(base64.b64decode(image_base64))
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка генерации изображения: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
@dp.message(Command("draw"))
|
||||
async def draw(message: Message):
|
||||
save_message(message.chat.id, message.message_id)
|
||||
if message.from_user.id in Config.BAN:
|
||||
msg = await message.reply("Вы в бане")
|
||||
save_message(msg.chat.id, msg.message_id)
|
||||
else:
|
||||
user_prompt = message.text.replace("/draw", "").strip()
|
||||
if not user_prompt:
|
||||
confirm_msg = await message.answer("❗ Укажи промт после команды /draw")
|
||||
save_message(confirm_msg.chat.id, confirm_msg.message_id)
|
||||
return
|
||||
|
||||
en_prompt = await translate_to_en(user_prompt)
|
||||
logger.info(f"Промт переведен: {user_prompt} -> {en_prompt}")
|
||||
|
||||
img_bytes = await generate_image(en_prompt)
|
||||
if img_bytes:
|
||||
img_bytes.seek(0)
|
||||
photo = BufferedInputFile(img_bytes.read(), filename="result.png")
|
||||
msg = await bot.send_photo(chat_id=message.chat.id, photo=photo)
|
||||
save_message(msg.chat.id, msg.message_id)
|
||||
else:
|
||||
error_msg = await message.answer("⚠️ Ошибка при генерации изображения.")
|
||||
save_message(error_msg.chat.id, error_msg.message_id)
|
||||
|
||||
@dp.message(Command("img2img"))
|
||||
@saving
|
||||
async def img2img_with_caption(message: Message, bot: Bot):
|
||||
raw_caption = message.caption or ""
|
||||
user_prompt = raw_caption.replace("/img2img", "").strip()
|
||||
if not user_prompt:
|
||||
await message.answer(
|
||||
"❗ Укажи промт в подписи к фото после команды /img2img"
|
||||
)
|
||||
return
|
||||
|
||||
en_prompt = await translate_to_en(user_prompt)
|
||||
logger.info(f"Промт для img2img переведен: {user_prompt} -> {en_prompt}")
|
||||
|
||||
try:
|
||||
if message.photo:
|
||||
# Берём последнее (самое большое) фото
|
||||
photo = message.photo[-1].file_id
|
||||
|
||||
# Отправляем в SD API по file_id (как в iadmin)
|
||||
# Здесь отличие: SD API требует base64, поэтому file_id нужно скачать
|
||||
# Но логика построена как в iadmin — сначала берём file_id
|
||||
file = await bot.get_file(photo)
|
||||
file_bytes = await bot.download_file(file.file_path)
|
||||
|
||||
img_bytes = await generate_img2img(
|
||||
en_prompt, BytesIO(file_bytes.read())
|
||||
)
|
||||
if img_bytes:
|
||||
img_bytes.seek(0)
|
||||
photo = BufferedInputFile(
|
||||
img_bytes.read(), filename="img2img_result.png"
|
||||
)
|
||||
msg = await bot.send_photo(chat_id=message.chat.id, photo=photo)
|
||||
else:
|
||||
msg = await message.answer("⚠️ Ошибка при img2img генерации.")
|
||||
|
||||
else:
|
||||
msg = await message.answer("❗ Пришли фото с подписью /img2img <промт>")
|
||||
save_message(msg.chat.id, msg.message_id)
|
||||
except Exception as e:
|
||||
await message.answer(f"⚠️ Ошибка: {e}")
|
||||
@@ -1,7 +1,9 @@
|
||||
def register(dp, state, bot):
|
||||
from . import handlers
|
||||
|
||||
handlers.register_handlers(dp, state, bot)
|
||||
|
||||
|
||||
def unregister(dp):
|
||||
# Здесь можно удалить хендлеры, если нужно
|
||||
dp.message_handlers.handlers.clear()
|
||||
|
||||
@@ -5,6 +5,7 @@ import logging
|
||||
import glob
|
||||
import json
|
||||
import requests
|
||||
from typing import Optional
|
||||
|
||||
from mutagen.easyid3 import EasyID3
|
||||
from mutagen.id3 import ID3, APIC, error
|
||||
@@ -16,13 +17,14 @@ async def get_video_info(url: str) -> dict:
|
||||
"""Получает информацию о видео через yt-dlp"""
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
'yt-dlp',
|
||||
'--dump-json',
|
||||
'--no-playlist',
|
||||
'--cookies', '~/myfirstprogramm/addons/example_addon/cookies.txt',
|
||||
"yt-dlp",
|
||||
"--dump-json",
|
||||
"--no-playlist",
|
||||
"--cookies",
|
||||
"~/myfirstprogramm/addons/example_addon/cookies.txt",
|
||||
url,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
stdout, stderr = await process.communicate()
|
||||
@@ -33,17 +35,19 @@ async def get_video_info(url: str) -> dict:
|
||||
return None
|
||||
|
||||
|
||||
async def download_thumbnail(thumbnail_url: str) -> tuple[bytes, str]:
|
||||
async def download_thumbnail(
|
||||
thumbnail_url: str,
|
||||
) -> tuple[Optional[bytes], Optional[str]]:
|
||||
"""Скачивает обложку видео и возвращает данные и MIME тип"""
|
||||
try:
|
||||
response = requests.get(thumbnail_url, timeout=10)
|
||||
if response.status_code == 200:
|
||||
if 'jpeg' in thumbnail_url or 'jpg' in thumbnail_url:
|
||||
mime_type = 'image/jpeg'
|
||||
elif 'png' in thumbnail_url:
|
||||
mime_type = 'image/png'
|
||||
if "jpeg" in thumbnail_url or "jpg" in thumbnail_url:
|
||||
mime_type = "image/jpeg"
|
||||
elif "png" in thumbnail_url:
|
||||
mime_type = "image/png"
|
||||
else:
|
||||
mime_type = response.headers.get('Content-Type', 'image/jpeg')
|
||||
mime_type = response.headers.get("Content-Type", "image/jpeg")
|
||||
return response.content, mime_type
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось скачать обложку: {e}")
|
||||
@@ -59,19 +63,19 @@ def apply_metadata(mp3_path: str, metadata: dict):
|
||||
audio = EasyID3()
|
||||
audio.save(mp3_path)
|
||||
|
||||
audio['title'] = metadata.get('title', 'Unknown Title')
|
||||
audio['artist'] = metadata.get('performer', 'Unknown Artist')
|
||||
audio["title"] = metadata.get("title", "Unknown Title")
|
||||
audio["artist"] = metadata.get("performer", "Unknown Artist")
|
||||
audio.save(mp3_path)
|
||||
|
||||
if metadata.get('thumbnail_data'):
|
||||
if metadata.get("thumbnail_data"):
|
||||
audio = ID3(mp3_path)
|
||||
audio.add(
|
||||
APIC(
|
||||
encoding=3,
|
||||
mime=metadata.get('thumbnail_mime', 'image/jpeg'),
|
||||
mime=metadata.get("thumbnail_mime", "image/jpeg"),
|
||||
type=3, # front cover
|
||||
desc='Cover',
|
||||
data=metadata['thumbnail_data']
|
||||
desc="Cover",
|
||||
data=metadata["thumbnail_data"],
|
||||
)
|
||||
)
|
||||
audio.save(mp3_path)
|
||||
@@ -96,27 +100,32 @@ async def download_mp3_isolated(url: str) -> tuple[str, dict]:
|
||||
duration = 0
|
||||
|
||||
if video_info:
|
||||
title = video_info.get('title', 'Unknown Title')
|
||||
uploader = video_info.get('uploader', 'Unknown Artist')
|
||||
thumbnail_url = video_info.get('thumbnail')
|
||||
if not thumbnail_url and video_info.get('thumbnails'):
|
||||
thumbnails = video_info.get('thumbnails', [])
|
||||
title = video_info.get("title", "Unknown Title")
|
||||
uploader = video_info.get("uploader", "Unknown Artist")
|
||||
thumbnail_url = video_info.get("thumbnail")
|
||||
if not thumbnail_url and video_info.get("thumbnails"):
|
||||
thumbnails = video_info.get("thumbnails", [])
|
||||
if thumbnails:
|
||||
thumbnail_url = thumbnails[-1].get('url')
|
||||
duration = video_info.get('duration', 0)
|
||||
thumbnail_url = thumbnails[-1].get("url")
|
||||
duration = video_info.get("duration", 0)
|
||||
logger.info(f"Получена информация о видео: {title}")
|
||||
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
'yt-dlp',
|
||||
'-x', '--audio-format', 'mp3',
|
||||
'--cookies', '~/myfirstprogramm/addons/example_addon/cookies.txt',
|
||||
'--audio-quality', '320K',
|
||||
'--no-playlist',
|
||||
'-o', output_template,
|
||||
'--ignore-errors',
|
||||
"yt-dlp",
|
||||
"-x",
|
||||
"--audio-format",
|
||||
"mp3",
|
||||
"--cookies",
|
||||
"~/myfirstprogramm/addons/example_addon/cookies.txt",
|
||||
"--audio-quality",
|
||||
"320K",
|
||||
"--no-playlist",
|
||||
"-o",
|
||||
output_template,
|
||||
"--ignore-errors",
|
||||
url,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=300)
|
||||
@@ -125,24 +134,31 @@ async def download_mp3_isolated(url: str) -> tuple[str, dict]:
|
||||
if mp3_files:
|
||||
actual_file = mp3_files[0]
|
||||
if os.path.getsize(actual_file) > 1000:
|
||||
with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as final_file:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
suffix=".mp3", delete=False
|
||||
) as final_file:
|
||||
final_filename = final_file.name
|
||||
|
||||
with open(actual_file, 'rb') as src, open(final_filename, 'wb') as dst:
|
||||
with (
|
||||
open(actual_file, "rb") as src,
|
||||
open(final_filename, "wb") as dst,
|
||||
):
|
||||
dst.write(src.read())
|
||||
|
||||
thumbnail_data, mime_type = None, None
|
||||
if thumbnail_url:
|
||||
thumbnail_data, mime_type = await download_thumbnail(thumbnail_url)
|
||||
thumbnail_data, mime_type = await download_thumbnail(
|
||||
thumbnail_url
|
||||
)
|
||||
if thumbnail_data:
|
||||
logger.info(f"Обложка скачана: {thumbnail_url}")
|
||||
|
||||
metadata = {
|
||||
'title': title,
|
||||
'performer': uploader,
|
||||
'duration': duration,
|
||||
'thumbnail_data': thumbnail_data,
|
||||
'thumbnail_mime': mime_type
|
||||
"title": title,
|
||||
"performer": uploader,
|
||||
"duration": duration,
|
||||
"thumbnail_data": thumbnail_data,
|
||||
"thumbnail_mime": mime_type,
|
||||
}
|
||||
|
||||
# Прописываем теги в MP3
|
||||
|
||||
@@ -3,11 +3,14 @@ from aiogram.filters import Command
|
||||
from models.state import BotState
|
||||
from utils.antispam import admin_required
|
||||
from logging import getLogger
|
||||
from .dowloadmp3_to_youtube import *
|
||||
from .dowloadmp3_to_youtube import download_mp3_isolated
|
||||
import tempfile
|
||||
import asyncio
|
||||
from os import path, unlink
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
@dp.message(Command("dowmp3"))
|
||||
@admin_required(5)
|
||||
@@ -18,7 +21,9 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
return
|
||||
|
||||
url = args[1]
|
||||
logger.info(f"Получена команда /dowmp3 от user_id={message.from_user.id}, url={url}")
|
||||
logger.info(
|
||||
f"Получена команда /dowmp3 от user_id={message.from_user.id}, url={url}"
|
||||
)
|
||||
|
||||
status_msg = await message.reply("⏳ Скачиваю аудио... Это займет 1-2 минуты")
|
||||
|
||||
@@ -29,30 +34,32 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
if file_size < 1000:
|
||||
raise Exception("Файл слишком маленький")
|
||||
|
||||
await status_msg.edit_text(f"✅ Аудио готово! Отправляю...")
|
||||
await status_msg.edit_text("✅ Аудио готово! Отправляю...")
|
||||
|
||||
# Подготавливаем аудио файл
|
||||
audio_input = types.FSInputFile(filename)
|
||||
|
||||
# Базовые параметры
|
||||
send_params = {
|
||||
'audio': audio_input,
|
||||
'title': metadata['title'][:64],
|
||||
'performer': metadata['performer'][:64],
|
||||
'duration': int(metadata['duration']) if metadata['duration'] else None,
|
||||
'caption': f"🎵 {metadata['title']}\n👤 {metadata['performer']}"
|
||||
"audio": audio_input,
|
||||
"title": metadata["title"][:64],
|
||||
"performer": metadata["performer"][:64],
|
||||
"duration": int(metadata["duration"]) if metadata["duration"] else None,
|
||||
"caption": f"🎵 {metadata['title']}\n👤 {metadata['performer']}",
|
||||
}
|
||||
|
||||
# Добавляем обложку если есть
|
||||
if metadata['thumbnail_data']:
|
||||
if metadata["thumbnail_data"]:
|
||||
try:
|
||||
# Создаем временный файл для обложки
|
||||
with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as thumb_file:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
suffix=".jpg", delete=False
|
||||
) as thumb_file:
|
||||
thumb_filename = thumb_file.name
|
||||
thumb_file.write(metadata['thumbnail_data'])
|
||||
thumb_file.write(metadata["thumbnail_data"])
|
||||
|
||||
# Используем FSInputFile для обложки
|
||||
send_params['thumbnail'] = types.FSInputFile(thumb_filename)
|
||||
send_params["thumbnail"] = types.FSInputFile(thumb_filename)
|
||||
logger.info("Обложка добавлена к сообщению")
|
||||
|
||||
except Exception as e:
|
||||
@@ -62,7 +69,7 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
await message.answer_audio(**send_params)
|
||||
|
||||
# Удаляем временный файл обложки если создавали
|
||||
if 'thumb_filename' in locals() and path.exists(thumb_filename):
|
||||
if "thumb_filename" in locals() and path.exists(thumb_filename):
|
||||
unlink(thumb_filename)
|
||||
|
||||
await status_msg.delete()
|
||||
@@ -74,8 +81,8 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
await status_msg.edit_text(f"❌ Ошибка: {str(e)}")
|
||||
logger.error(f"Ошибка при скачивании: {e}")
|
||||
finally:
|
||||
if 'filename' in locals() and path.exists(filename):
|
||||
if "filename" in locals() and path.exists(filename):
|
||||
try:
|
||||
unlink(filename)
|
||||
except:
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
def register(dp, state, bot):
|
||||
from . import handlers
|
||||
|
||||
handlers.register_handlers(dp, state, bot)
|
||||
|
||||
|
||||
def unregister(dp):
|
||||
# Здесь можно удалить хендлеры, если нужно
|
||||
dp.message_handlers.handlers.clear()
|
||||
@@ -0,0 +1,105 @@
|
||||
import base64
|
||||
import aiohttp
|
||||
import logging
|
||||
from aiogram import Dispatcher, Bot
|
||||
from aiogram.types import Message
|
||||
from utils.antispam import saving, save_message
|
||||
from aiogram.filters import Command
|
||||
from models.state import BotState
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
chat_history = {}
|
||||
MAX_HISTORY = 20 # храним последние 20 сообщений (user+assistant)
|
||||
|
||||
@dp.message(Command("gpt"))
|
||||
@saving
|
||||
async def ask_gpt(message: Message):
|
||||
chat_id = message.chat.id
|
||||
if chat_id not in chat_history:
|
||||
chat_history[chat_id] = []
|
||||
|
||||
content_blocks = []
|
||||
user_prompt = None
|
||||
|
||||
# Текст после команды или caption
|
||||
if message.text:
|
||||
parts = message.text.split(maxsplit=1)
|
||||
if len(parts) > 1:
|
||||
user_prompt = parts[1]
|
||||
if message.caption:
|
||||
user_prompt = message.caption
|
||||
|
||||
if user_prompt:
|
||||
content_blocks.append({"type": "text", "text": user_prompt})
|
||||
|
||||
# Фото → base64 → image_url
|
||||
if message.photo:
|
||||
photo = message.photo[-1]
|
||||
file = await bot.get_file(photo.file_id)
|
||||
file_bytes = await bot.download_file(file.file_path)
|
||||
image_b64 = base64.b64encode(file_bytes.read()).decode("utf-8")
|
||||
content_blocks.append(
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {"url": f"data:image/jpeg;base64,{image_b64}"},
|
||||
}
|
||||
)
|
||||
|
||||
if not content_blocks:
|
||||
await message.reply("❗ Укажи текст или прикрепи фото")
|
||||
return
|
||||
|
||||
url = "http://192.168.31.197:1234/v1/chat/completions"
|
||||
|
||||
# Добавляем новое сообщение в историю
|
||||
chat_history[chat_id].append({"role": "user", "content": content_blocks})
|
||||
|
||||
# Ограничиваем историю (оставляем последние MAX_HISTORY сообщений)
|
||||
if len(chat_history[chat_id]) > MAX_HISTORY:
|
||||
chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:]
|
||||
|
||||
payload = {
|
||||
"model": "qwen/qwen3-vl-4b",
|
||||
"messages": chat_history[ chat_id],
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 4096,
|
||||
"stream": False,
|
||||
}
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, json=payload) as resp:
|
||||
if resp.status != 200:
|
||||
error_text = await resp.text()
|
||||
await message.reply(
|
||||
f"❌ Ошибка LM Studio: {resp.status} {error_text}"
|
||||
)
|
||||
return
|
||||
|
||||
data = await resp.json()
|
||||
reply_text = data["choices"][0]["message"]["content"]
|
||||
|
||||
# Сохраняем ответ ассистента в историю
|
||||
chat_history[chat_id].append(
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": [{"type": "text", "text": reply_text}],
|
||||
}
|
||||
)
|
||||
|
||||
# Ограничиваем снова (чтобы не разрасталось)
|
||||
if len(chat_history[chat_id]) > MAX_HISTORY:
|
||||
chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:]
|
||||
|
||||
msg = await message.reply(f"🤖 Ответ:\n{reply_text}")
|
||||
save_message(msg.chat.id, msg.message_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при запросе к LM Studio: {e}")
|
||||
await message.reply(f"❌ Ошибка при запросе к LM Studio: {e}")
|
||||
|
||||
@dp.message(Command("clear"))
|
||||
async def clear(message: Message):
|
||||
chat_history.pop(message.chat.id, None)
|
||||
@@ -1,7 +1,9 @@
|
||||
def register(dp, state, bot):
|
||||
from . import handlers
|
||||
|
||||
handlers.register_handlers(dp, state, bot)
|
||||
|
||||
|
||||
def unregister(dp):
|
||||
# Здесь можно удалить хендлеры, если нужно
|
||||
dp.message_handlers.handlers.clear()
|
||||
|
||||
@@ -5,10 +5,11 @@ from models.state import BotState
|
||||
from config import Config
|
||||
import logging
|
||||
from utils.antispam import admin_required
|
||||
from storage.message_storage import save_message # импортируем функцию
|
||||
from storage.message_storage import save_message # импортируем функцию
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
@dp.message(Command("hello"))
|
||||
@admin_required(1)
|
||||
@@ -20,8 +21,7 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
try:
|
||||
name = Config.Names.get(admin_id, "Админ")
|
||||
msg = await bot.send_message(
|
||||
chat_id=admin_id,
|
||||
text=f"🤖 Я готов к работе, господин {name}!"
|
||||
chat_id=admin_id, text=f"🤖 Я готов к работе, господин {name}!"
|
||||
)
|
||||
# сохраняем сообщение, отправленное админу
|
||||
save_message(msg.chat.id, msg.message_id)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
def register(dp, state, bot):
|
||||
from . import handlers
|
||||
|
||||
handlers.register_handlers(dp, state, bot)
|
||||
|
||||
|
||||
def unregister(dp):
|
||||
# Здесь можно удалить хендлеры, если нужно
|
||||
dp.message_handlers.handlers.clear()
|
||||
|
||||
@@ -9,9 +9,10 @@ API_URL = "http://127.0.0.1:7700/speak"
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
@dp.message(Command("id"))
|
||||
@saving
|
||||
async def id(message: Message):
|
||||
id = message.from_user.id
|
||||
msg = await message.reply(str(id))
|
||||
msg = await message.reply(str(id))
|
||||
|
||||
@@ -2,6 +2,7 @@ import importlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class AddonManager:
|
||||
def __init__(self, dp, state, bot):
|
||||
self.dp = dp
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
def register(dp, state, bot):
|
||||
from . import handlers
|
||||
|
||||
handlers.register_handlers(dp, state, bot)
|
||||
|
||||
|
||||
def unregister(dp):
|
||||
# Здесь можно удалить хендлеры, если нужно
|
||||
dp.message_handlers.handlers.clear()
|
||||
|
||||
+23
-30
@@ -1,6 +1,4 @@
|
||||
from config import Config
|
||||
import aiohttp
|
||||
from aiogram.types import BufferedInputFile
|
||||
from utils.antispam import admin_required
|
||||
from aiogram import Dispatcher, Bot
|
||||
from aiogram.types import Message
|
||||
@@ -11,22 +9,7 @@ from aiogram.types import PollAnswer
|
||||
from storage.message_storage import save_message
|
||||
|
||||
logger = getLogger(__name__)
|
||||
API_URL = "http://127.0.0.1:7700/speak"
|
||||
|
||||
from config import Config
|
||||
import aiohttp
|
||||
from aiogram.types import BufferedInputFile
|
||||
from utils.antispam import admin_required
|
||||
from aiogram import Dispatcher, Bot
|
||||
from aiogram.types import Message
|
||||
from models.state import BotState
|
||||
from aiogram.filters import Command
|
||||
from logging import getLogger
|
||||
from aiogram.types import PollAnswer
|
||||
from storage.message_storage import save_message
|
||||
|
||||
logger = getLogger(__name__)
|
||||
API_URL = "http://127.0.0.1:7700/speak"
|
||||
|
||||
def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
@dp.message(Command("poll"))
|
||||
@@ -37,9 +20,12 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
poll_msg = await bot.send_poll(
|
||||
chat_id=chat_id,
|
||||
question="Кто опоздает?",
|
||||
options=["Я", "Не знаю", "Наверное"],
|
||||
options=["Я", "Я очень сильно опоздаю", "Наверное"],
|
||||
is_anonymous=False,
|
||||
allows_multiple_answers=False
|
||||
allows_multiple_answers=False,
|
||||
)
|
||||
await bot.pin_chat_message(
|
||||
chat_id, poll_msg.message_id, disable_notification=False
|
||||
)
|
||||
# сохраняем сам опрос
|
||||
save_message(poll_msg.chat.id, poll_msg.message_id)
|
||||
@@ -48,9 +34,6 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке в чат {chat_id}: {e}")
|
||||
|
||||
confirm_msg = await message.answer("✅ Сообщение отправлено.")
|
||||
save_message(confirm_msg.chat.id, confirm_msg.message_id)
|
||||
|
||||
@dp.poll_answer()
|
||||
async def handle_poll_answer(poll_answer: PollAnswer):
|
||||
user = poll_answer.user
|
||||
@@ -60,20 +43,30 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
username = f"@{user.username}" if user.username else user.first_name
|
||||
|
||||
# всегда пишем в первый чат из Config.CHAT_IDS
|
||||
# 6394047531
|
||||
|
||||
if option_ids and option_ids[0] == 0:
|
||||
msg = await bot.send_message(
|
||||
chat_id=6394047531,
|
||||
text=f"{username} опоздает"
|
||||
chat_id=6394047531, text=f"{username} опоздает"
|
||||
)
|
||||
save_message(msg.chat.id, msg.message_id)
|
||||
elif option_ids and option_ids[0] == 1:
|
||||
msg = await bot.send_message(
|
||||
chat_id=6394047531, text=f"{username} сильно опоздает"
|
||||
)
|
||||
save_message(msg.chat.id, msg.message_id)
|
||||
elif option_ids and option_ids[0] == 2:
|
||||
msg = await bot.send_message(
|
||||
chat_id=6394047531, text=f"{username} возможно опоздает"
|
||||
)
|
||||
save_message(msg.chat.id, msg.message_id)
|
||||
elif not option_ids:
|
||||
msg = await bot.send_message(
|
||||
chat_id=6394047531, text=f"{username} Отменил свой голос"
|
||||
)
|
||||
save_message(msg.chat.id, msg.message_id)
|
||||
else:
|
||||
msg = await bot.send_message(
|
||||
chat_id=6394047531,
|
||||
text=f"{username} выбрал вариант {option_ids}"
|
||||
chat_id=6394047531, text=f"{username} выбрал вариант {option_ids}"
|
||||
)
|
||||
save_message(msg.chat.id, msg.message_id)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
def register(dp, state, bot):
|
||||
from . import handlers
|
||||
|
||||
handlers.register_handlers(dp, state, bot)
|
||||
|
||||
|
||||
def unregister(dp):
|
||||
# Здесь можно удалить хендлеры, если нужно
|
||||
dp.message_handlers.handlers.clear()
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
from config import Config
|
||||
import aiohttp
|
||||
import ssl
|
||||
import certifi
|
||||
from aiogram.types import BufferedInputFile
|
||||
from utils.antispam import admin_required
|
||||
from storage.message_storage import save_message
|
||||
from utils.antispam import admin_required, saving
|
||||
from aiogram import Dispatcher, Bot
|
||||
from aiogram.types import Message
|
||||
from models.state import BotState
|
||||
from aiogram.filters import Command
|
||||
from logging import getLogger
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
API_URL = "http://127.0.0.1:7700/speak"
|
||||
|
||||
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
||||
|
||||
|
||||
def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
@dp.message(Command("vadmin"))
|
||||
@admin_required(0)
|
||||
@@ -21,11 +27,26 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
return
|
||||
phrase = parts[1]
|
||||
|
||||
# Запрос к TTS API
|
||||
# URL с параметрами модели и голоса
|
||||
url = f"{Config.DEEPGRAM_TTS_URL}model=aura-2-andromeda-en"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Token {Config.DEEPGRAM_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# В JSON только text
|
||||
payload = {"text": phrase}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(API_URL, json={"text": phrase}) as resp:
|
||||
async with session.post(
|
||||
url, headers=headers, json=payload, ssl=ssl_context
|
||||
) as resp:
|
||||
if resp.status != 200:
|
||||
await message.reply("Ошибка генерации аудио")
|
||||
error_text = await resp.text()
|
||||
await message.reply(
|
||||
f"Ошибка генерации аудио: {resp.status} {error_text}"
|
||||
)
|
||||
return
|
||||
audio_bytes = await resp.read()
|
||||
audio_file = BufferedInputFile(audio_bytes, filename="speech.wav")
|
||||
@@ -43,8 +64,12 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
@admin_required(0)
|
||||
async def admin(message: Message):
|
||||
raw_text = message.text or message.caption
|
||||
if not raw_text and not (message.photo or message.document or message.audio or message.video):
|
||||
await message.reply("❌ Укажи текст или прикрепи файл/медиа: /admin <сообщение>")
|
||||
if not raw_text and not (
|
||||
message.photo or message.document or message.audio or message.video
|
||||
):
|
||||
await message.reply(
|
||||
"❌ Укажи текст или прикрепи файл/медиа: /admin <сообщение>"
|
||||
)
|
||||
return
|
||||
|
||||
# Отрезаем саму команду (/admin)
|
||||
@@ -60,15 +85,21 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
|
||||
elif message.document:
|
||||
# Документ
|
||||
await bot.send_document(chat_id, message.document.file_id, caption=text_to_send)
|
||||
await bot.send_document(
|
||||
chat_id, message.document.file_id, caption=text_to_send
|
||||
)
|
||||
|
||||
elif message.audio:
|
||||
# Аудио (музыка)
|
||||
await bot.send_audio(chat_id, message.audio.file_id, caption=text_to_send)
|
||||
await bot.send_audio(
|
||||
chat_id, message.audio.file_id, caption=text_to_send
|
||||
)
|
||||
|
||||
elif message.video:
|
||||
# Видео
|
||||
await bot.send_video(chat_id, message.video.file_id, caption=text_to_send)
|
||||
await bot.send_video(
|
||||
chat_id, message.video.file_id, caption=text_to_send
|
||||
)
|
||||
|
||||
else:
|
||||
# Только текст
|
||||
@@ -85,8 +116,12 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
@admin_required(0)
|
||||
async def id_admin(message: Message):
|
||||
raw_text = message.text or message.caption
|
||||
if not raw_text and not (message.photo or message.document or message.audio or message.video):
|
||||
await message.reply("❌ Укажи ID чата и текст или прикрепи файл/медиа: /iadmin <chat_id> <сообщение>")
|
||||
if not raw_text and not (
|
||||
message.photo or message.document or message.audio or message.video
|
||||
):
|
||||
await message.reply(
|
||||
"❌ Укажи ID чата и текст или прикрепи файл/медиа: /iadmin <chat_id> <сообщение>"
|
||||
)
|
||||
return
|
||||
|
||||
# Отрезаем саму команду (/iadmin)
|
||||
@@ -111,19 +146,25 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
||||
|
||||
elif message.document:
|
||||
# Документ
|
||||
await bot.send_document(chat_id, message.document.file_id, caption=text_to_send)
|
||||
await bot.send_document(
|
||||
chat_id, message.document.file_id, caption=text_to_send
|
||||
)
|
||||
|
||||
elif message.audio:
|
||||
# Аудио (музыка)
|
||||
await bot.send_audio(chat_id, message.audio.file_id, caption=text_to_send)
|
||||
await bot.send_audio(
|
||||
chat_id, message.audio.file_id, caption=text_to_send
|
||||
)
|
||||
|
||||
elif message.video:
|
||||
# Видео
|
||||
await bot.send_video(chat_id, message.video.file_id, caption=text_to_send)
|
||||
await bot.send_video(
|
||||
chat_id, message.video.file_id, caption=text_to_send
|
||||
)
|
||||
|
||||
else:
|
||||
# Только текст
|
||||
await bot.send_message(chat_id, text_to_send)
|
||||
await bot.send_message(chat_id, text_to_send, parse_mode="Markdown")
|
||||
|
||||
logger.info(f"Сообщение отправлено в чат {chat_id}")
|
||||
await message.answer("✅ Сообщение отправлено.")
|
||||
|
||||
Reference in New Issue
Block a user