- aiogram 3 bot with commands /start /check /status /stop - InstagramMonitor: periodic check of followees stories and posts - Docker deployment with docker-compose - State tracking to avoid duplicate sends - Auto-cleanup of old media files
134 lines
4.4 KiB
Python
134 lines
4.4 KiB
Python
import asyncio
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from aiogram import Bot, Dispatcher, F, Router
|
|
from aiogram.filters import CommandStart
|
|
from aiogram.types import FSInputFile, Message
|
|
from loguru import logger
|
|
|
|
from config import config
|
|
from instagram_monitor import InstagramMonitor
|
|
|
|
router = Router()
|
|
bot = Bot(token=config.telegram_bot_token)
|
|
monitor = InstagramMonitor()
|
|
|
|
|
|
@router.message(CommandStart())
|
|
async def cmd_start(message: Message) -> None:
|
|
if message.from_user and message.from_user.id == config.telegram_chat_id:
|
|
await message.answer(
|
|
"Instagram Monitor Bot запущен.\n"
|
|
f"Проверяю подписки каждые {config.check_interval_minutes} минут.\n"
|
|
"Команды:\n"
|
|
"/check — проверить сейчас\n"
|
|
"/status — статус бота\n"
|
|
"/stop — остановить мониторинг"
|
|
)
|
|
else:
|
|
await message.answer("Этот бот не для тебя.")
|
|
|
|
|
|
@router.message(F.text == "/status")
|
|
async def cmd_status(message: Message) -> None:
|
|
if message.from_user and message.from_user.id == config.telegram_chat_id:
|
|
state_file = Path(config.download_dir) / "state.json"
|
|
tracked = 0
|
|
if state_file.exists():
|
|
import json
|
|
with open(state_file) as f:
|
|
tracked = len(json.load(f))
|
|
await message.answer(
|
|
f"Отслежено медиа: {tracked}\n"
|
|
f"Интервал: {config.check_interval_minutes} мин\n"
|
|
f"Instagram: {config.instagram_username}"
|
|
)
|
|
|
|
|
|
@router.message(F.text == "/stop")
|
|
async def cmd_stop(message: Message) -> None:
|
|
if message.from_user and message.from_user.id == config.telegram_chat_id:
|
|
await message.answer("Останавливаю мониторинг...")
|
|
import sys
|
|
sys.exit(0)
|
|
|
|
|
|
@router.message(F.text == "/check")
|
|
async def cmd_check(message: Message) -> None:
|
|
if message.from_user and message.from_user.id == config.telegram_chat_id:
|
|
await message.answer("Проверяю обновления...")
|
|
count = 0
|
|
async for update in monitor.check_updates():
|
|
await send_media(update)
|
|
count += 1
|
|
await message.answer(f"Готово. Найдено: {count}")
|
|
|
|
|
|
async def send_media(update: dict) -> None:
|
|
chat_id = config.telegram_chat_id
|
|
media_path = update["media_path"]
|
|
username = update["username"]
|
|
media_type = update["type"]
|
|
caption = update["caption"]
|
|
shortcode = update["shortcode"]
|
|
|
|
header = f"{'Сторис' if media_type == 'story' else 'Пост'} от @{username}"
|
|
|
|
if caption:
|
|
text = f"{header}\n\n{caption}"
|
|
else:
|
|
text = header
|
|
|
|
path = Path(media_path)
|
|
if not path.exists():
|
|
logger.warning(f"Media file not found: {media_path}")
|
|
return
|
|
|
|
try:
|
|
if path.suffix == ".mp4":
|
|
video = FSInputFile(str(path))
|
|
await bot.send_video(chat_id=chat_id, video=video, caption=text, parse_mode=None)
|
|
elif path.suffix in (".jpg", ".jpeg", ".png", ".webp"):
|
|
photo = FSInputFile(str(path))
|
|
await bot.send_photo(chat_id=chat_id, photo=photo, caption=text, parse_mode=None)
|
|
else:
|
|
document = FSInputFile(str(path))
|
|
await bot.send_document(chat_id=chat_id, document=document, caption=text, parse_mode=None)
|
|
except Exception as e:
|
|
logger.error(f"Failed to send media: {e}")
|
|
await bot.send_message(chat_id=chat_id, text=f"{header}\n\n{caption}\n\n[Файл не удалось отправить]")
|
|
|
|
|
|
async def monitor_loop() -> None:
|
|
logger.info("Starting monitor loop")
|
|
while True:
|
|
try:
|
|
async for update in monitor.check_updates():
|
|
await send_media(update)
|
|
monitor.cleanup_downloads(max_age_hours=48)
|
|
except Exception as e:
|
|
logger.error(f"Monitor loop error: {e}")
|
|
await asyncio.sleep(config.check_interval_minutes * 60)
|
|
|
|
|
|
async def main() -> None:
|
|
errors = config.validate()
|
|
if errors:
|
|
for e in errors:
|
|
logger.error(e)
|
|
return
|
|
|
|
dp = Dispatcher()
|
|
dp.include_router(router)
|
|
|
|
asyncio.create_task(monitor_loop())
|
|
|
|
logger.info("Bot started")
|
|
await dp.start_polling(bot)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import asyncio
|
|
asyncio.run(main())
|