мультипоток. очистка файлов. дополнил описание /start про группы

This commit is contained in:
vrubelroman 2025-12-10 21:05:27 +03:00
parent 5acd8fd9db
commit 8024eea868
2 changed files with 121 additions and 31 deletions

120
bot.py
View file

@ -9,6 +9,7 @@ import subprocess
from pathlib import Path
from urllib.parse import urlparse
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor
import yt_dlp
import httpx
@ -40,6 +41,10 @@ DATA_DIR = BASE_DIR / 'data'
DATA_DIR.mkdir(parents=True, exist_ok=True)
DB_FILE = DATA_DIR / 'bot.db'
# ThreadPoolExecutor для выполнения блокирующих операций (скачивание видео)
# Позволяет обрабатывать несколько запросов параллельно
DOWNLOAD_EXECUTOR = ThreadPoolExecutor(max_workers=5, thread_name_prefix="download")
def init_database():
"""Инициализирует базу данных и создает таблицы если их нет"""
try:
@ -169,6 +174,37 @@ def extract_urls_from_text(text: str) -> list[str]:
return urls
def cleanup_old_files(max_age_hours: int = 24):
"""Удаляет старые файлы и .part файлы из папки загрузок"""
try:
current_time = time.time()
max_age_seconds = max_age_hours * 3600
for file_path in DOWNLOADS_DIR.glob('*'):
if not file_path.is_file():
continue
# Удаляем все .part файлы (недокачанные)
if file_path.suffix == '.part':
try:
file_path.unlink()
logger.info(f"Удален .part файл: {file_path.name}")
except Exception as e:
logger.warning(f"Не удалось удалить .part файл {file_path.name}: {e}")
continue
# Удаляем старые файлы (старше max_age_hours)
try:
file_age = current_time - file_path.stat().st_mtime
if file_age > max_age_seconds:
file_path.unlink()
logger.info(f"Удален старый файл: {file_path.name} (возраст: {file_age/3600:.1f} часов)")
except Exception as e:
logger.warning(f"Не удалось проверить/удалить файл {file_path.name}: {e}")
except Exception as e:
logger.error(f"Ошибка при очистке старых файлов: {e}")
def _safe_filename(title: str, chat_id: int) -> str:
"""Создает безопасное имя файла"""
safe_title = re.sub(r'[<>:"/\\|?*]', '', title)[:100]
@ -203,8 +239,13 @@ async def download_youtube_video(url: str, chat_id: int, max_retries: int = 3) -
},
}
# Получаем информацию о видео в executor (неблокирующе)
def extract_info_sync():
with yt_dlp.YoutubeDL(ydl_opts_info) as ydl:
info = ydl.extract_info(url, download=False)
return ydl.extract_info(url, download=False)
loop = asyncio.get_event_loop()
info = await loop.run_in_executor(DOWNLOAD_EXECUTOR, extract_info_sync)
video_title = info.get('title', 'video')
logger.info(f"YouTube: получена информация о видео: {video_title}")
@ -232,15 +273,25 @@ async def download_youtube_video(url: str, chat_id: int, max_retries: int = 3) -
}
logger.info(f"YouTube: начинаем скачивание (попытка {attempt + 1}/{max_retries})")
with yt_dlp.YoutubeDL(ydl_opts_download) as ydl:
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, lambda: ydl.download([url]))
# Находим скачанный файл
# Скачиваем в executor (неблокирующе)
def download_sync():
with yt_dlp.YoutubeDL(ydl_opts_download) as ydl:
ydl.download([url])
await loop.run_in_executor(DOWNLOAD_EXECUTOR, download_sync)
# Находим скачанный файл (тоже в executor для консистентности)
def find_downloaded_file():
downloaded_files = list(DOWNLOADS_DIR.glob(f'{chat_id}_*'))
if downloaded_files:
downloaded_files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
return str(downloaded_files[0])
return None
video_path = await loop.run_in_executor(DOWNLOAD_EXECUTOR, find_downloaded_file)
if video_path:
return video_path
else:
raise Exception("Файл не был найден после скачивания")
@ -327,15 +378,25 @@ async def download_instagram_video(url: str, chat_id: int, max_retries: int = 3)
logger.info(f"Instagram: начинаем скачивание (попытка {attempt + 1}/{max_retries})")
# Скачиваем в executor (неблокирующе)
def download_sync():
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, lambda: ydl.download([url]))
ydl.download([url])
# Находим скачанный файл
loop = asyncio.get_event_loop()
await loop.run_in_executor(DOWNLOAD_EXECUTOR, download_sync)
# Находим скачанный файл (тоже в executor для консистентности)
def find_downloaded_file():
downloaded_files = list(DOWNLOADS_DIR.glob(f'{chat_id}_*'))
if downloaded_files:
downloaded_files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
return str(downloaded_files[0])
return None
video_path = await loop.run_in_executor(DOWNLOAD_EXECUTOR, find_downloaded_file)
if video_path:
return video_path
else:
raise Exception("Файл не был найден после скачивания")
@ -546,14 +607,12 @@ async def keep_instagram_session_alive():
'socket_timeout': 10,
}
def extract_info_sync():
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
return ydl.extract_info('https://www.instagram.com/', download=False)
loop = asyncio.get_event_loop()
await loop.run_in_executor(
None,
lambda: yt_dlp.YoutubeDL(ydl_opts).extract_info(
'https://www.instagram.com/',
download=False
)
)
await loop.run_in_executor(DOWNLOAD_EXECUTOR, extract_info_sync)
logger.info(f"Сессия Instagram успешно обновлена. Cookies действительны еще {days_left} дней")
except Exception as e:
@ -731,7 +790,20 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
except Exception as e:
logger.error(f"Ошибка: {e}")
error_msg = f"❌ Произошла ошибка при обработке видео:\n{str(e)}"
try:
await status_message.edit_text(error_msg)
except:
# Если status_message не существует, создаем новое сообщение
await update.message.reply_text(error_msg)
# При ошибке тоже пытаемся удалить временные файлы
try:
# Удаляем все .part файлы для этого chat_id
for part_file in DOWNLOADS_DIR.glob(f'{chat_id}_*.part'):
part_file.unlink()
logger.info(f"Удален .part файл после ошибки: {part_file.name}")
except Exception as cleanup_error:
logger.warning(f"Не удалось удалить .part файлы после ошибки: {cleanup_error}")
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
@ -749,6 +821,10 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
"• YouTube (youtube.com, youtu.be)\n"
"• Instagram (instagram.com)\n"
"• VK (vk.com)\n\n"
"👥 Работа в группах:\n"
"Добавь меня в группу и дай права администратора (нужно право на удаление сообщений). "
"После этого я буду автоматически находить ссылки на видео в сообщениях участников, "
"скачивать их и отправлять прямо в группу, заменяя исходное сообщение со ссылкой.\n\n"
"Команды:\n"
"/start - Начать работу\n"
"/stat - Статистика скачанных видео\n\n"
@ -777,6 +853,10 @@ def main():
# Инициализируем базу данных
init_database()
# Очищаем старые файлы при старте
logger.info("Очистка старых файлов при старте...")
cleanup_old_files(max_age_hours=1) # Удаляем файлы старше 1 часа
# Создаем приложение
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
@ -792,6 +872,16 @@ def main():
asyncio.create_task(keep_instagram_session_alive())
logger.info("Фоновая задача поддержания сессии Instagram запущена")
# Запускаем периодическую очистку файлов (каждые 6 часов)
async def periodic_cleanup():
while True:
await asyncio.sleep(6 * 3600) # 6 часов
cleanup_old_files(max_age_hours=1)
logger.info("Периодическая очистка старых файлов выполнена")
asyncio.create_task(periodic_cleanup())
logger.info("Фоновая задача периодической очистки файлов запущена")
application.post_init = post_init
# Запускаем бота

View file

@ -39,13 +39,13 @@ rusoska.com FALSE / FALSE 1799795493 userToken a01e24c3-c94f-4a4e-b11b-751b72046
.mozilla.org TRUE / FALSE 1799925684 _ga_B9CY1C9VBC GS2.1.s1765365263$o1$g1$t1765365684$j60$l0$h0
.mozilla.org TRUE / FALSE 1799925263 _ga GA1.2.1451822324.1765365263
.mozilla.org TRUE / FALSE 1765451663 _gid GA1.2.878207985.1765365263
.instagram.com TRUE / TRUE 1799947196 csrftoken CnChQ6nTz8cfm_U7q2ur9w
.instagram.com TRUE / TRUE 1799949717 csrftoken CnChQ6nTz8cfm_U7q2ur9w
.instagram.com TRUE / TRUE 1799925292 datr LFY5aVDEvvzQRTypNm_NZ0d3
.instagram.com TRUE / TRUE 1796901312 ig_did B0879634-89D6-4098-9B3E-958B6BC00183
.instagram.com TRUE / TRUE 1765970112 dpr 2
.instagram.com TRUE / TRUE 1799925293 mid aTlWLAAEAAEBRoS_PfrA_i5UP0w1
.instagram.com TRUE / TRUE 1765980504 wd 1920x944
.instagram.com TRUE / TRUE 1796911697 sessionid 42059678244%3AD0GdfKmaFZWqXp%3A10%3AAYieDJrvoWIE9WW--tzjgv-3EyrgI9XT6seopSdHFw
.instagram.com TRUE / TRUE 1773163196 ds_user_id 42059678244
.instagram.com TRUE / TRUE 0 rur "LDC\05442059678244\0541796923196:01fea3f1fb8a6a9f2cc556e3847d913f9a142263fa428807624c02963fddee2a5d36b728"
.instagram.com TRUE / TRUE 1773165717 ds_user_id 42059678244
.instagram.com TRUE / TRUE 0 rur "LDC\05442059678244\0541796925717:01fef99bf0a6a2eec6207d44260971856a393246d5f7590afa05e8b7183b7b873f243693"
addons.mozilla.org FALSE / TRUE 0 taarId 4dffa50e49cca797bb48f2f4f11803c251746ad45af1fef3ba1ad37379a24fea