- /debug @username shows raw API response for stories endpoint - Debug logging enabled for story fetch - /status now shows SQLite stats (story/post counts)
161 lines
5.4 KiB
Python
161 lines
5.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
|
||
import sys
|
||
|
||
logger.remove()
|
||
logger.add(sys.stderr, level="DEBUG")
|
||
|
||
from config import config
|
||
from instagram_monitor import InstagramMonitor, API_BASE
|
||
|
||
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:
|
||
stats = monitor.db.get_stats()
|
||
stats_text = ", ".join(f"{k}: {v}" for k, v in stats.items()) if stats else "пусто"
|
||
await message.answer(
|
||
f"В базе: {stats_text}\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[Файл не удалось отправить]")
|
||
|
||
|
||
@router.message(F.text.startswith("/debug"))
|
||
async def cmd_debug(message: Message) -> None:
|
||
if message.from_user and message.from_user.id == config.telegram_chat_id:
|
||
parts = message.text.split()
|
||
if len(parts) < 2:
|
||
await message.answer("Использование: /debug @username")
|
||
return
|
||
username = parts[1].lstrip("@")
|
||
await message.answer(f"Ищу @{username}...")
|
||
|
||
followees = monitor._get_followees()
|
||
user = next((u for u in followees if u["username"] == username), None)
|
||
if not user:
|
||
await message.answer(f"@{username} не найден в подписках")
|
||
return
|
||
|
||
user_pk = user["pk"]
|
||
await message.answer(f"user_pk: {user_pk}\nПроверяю сториз...")
|
||
|
||
resp = monitor.session.get(
|
||
f"{API_BASE}/feed/user/{user_pk}/story/",
|
||
params={"user_id": user_pk},
|
||
timeout=30,
|
||
)
|
||
await message.answer(f"HTTP {resp.status_code}\nResponse: {resp.text[:800]}")
|
||
|
||
|
||
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())
|