init
This commit is contained in:
commit
ec1112e162
22 changed files with 837 additions and 0 deletions
0
bot/handlers/__init__.py
Normal file
0
bot/handlers/__init__.py
Normal file
167
bot/handlers/analyze.py
Normal file
167
bot/handlers/analyze.py
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message
|
||||
from telethon import TelegramClient
|
||||
from telethon.errors import (
|
||||
ChannelInvalidError,
|
||||
ChannelPrivateError,
|
||||
FloodWaitError,
|
||||
UsernameInvalidError,
|
||||
UsernameNotOccupiedError,
|
||||
)
|
||||
|
||||
from bot.models import ReportType
|
||||
from bot.services.analyzer import analyze_channel
|
||||
from bot.services.chunker import chunk_messages
|
||||
from bot.services.fetcher import fetch_channel_messages
|
||||
from bot.services.formatter import split_report
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
# channel_username -> store temporarily per user for callback
|
||||
_pending: dict[int, str] = {}
|
||||
|
||||
|
||||
def _extract_channel(text: str) -> str | None:
|
||||
text = text.strip()
|
||||
# @username
|
||||
m = re.match(r"@(\w+)", text)
|
||||
if m:
|
||||
return m.group(1)
|
||||
# https://t.me/username
|
||||
m = re.match(r"https?://t\.me/(\w+)", text)
|
||||
if m:
|
||||
return m.group(1)
|
||||
# bare username
|
||||
if re.match(r"^\w+$", text):
|
||||
return text
|
||||
return None
|
||||
|
||||
|
||||
def _report_keyboard() -> InlineKeyboardMarkup:
|
||||
return InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[InlineKeyboardButton(text=rt.label, callback_data=f"report:{rt.value}")]
|
||||
for rt in ReportType
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@router.message(Command("analyze"))
|
||||
async def cmd_analyze(message: Message) -> None:
|
||||
args = (message.text or "").split(maxsplit=1)
|
||||
if len(args) < 2:
|
||||
await message.answer(
|
||||
"Please provide a channel: <code>/analyze @channel</code>",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
return
|
||||
|
||||
channel = _extract_channel(args[1])
|
||||
if not channel:
|
||||
await message.answer("Could not parse channel name. Use @username or t.me/username.")
|
||||
return
|
||||
|
||||
_pending[message.from_user.id] = channel
|
||||
await message.answer(
|
||||
f"Channel: <b>@{channel}</b>\n\nChoose report type:",
|
||||
parse_mode="HTML",
|
||||
reply_markup=_report_keyboard(),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("report:"))
|
||||
async def on_report_type(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
|
||||
user_id = callback.from_user.id
|
||||
channel = _pending.pop(user_id, None)
|
||||
if not channel:
|
||||
await callback.message.answer("Session expired. Please run /analyze again.")
|
||||
return
|
||||
|
||||
report_value = callback.data.split(":", 1)[1]
|
||||
report_type = ReportType(report_value)
|
||||
|
||||
telethon_client: TelegramClient = callback.message.bot.__dict__.get("_telethon_client")
|
||||
if not telethon_client:
|
||||
# Fallback: try dispatcher data
|
||||
from aiogram import Dispatcher
|
||||
# Access via bot's dispatcher isn't directly available in callback,
|
||||
# so we store it on the bot instance in __main__.py
|
||||
await callback.message.answer("Internal error: Telethon client not configured.")
|
||||
return
|
||||
|
||||
status_msg = await callback.message.answer(
|
||||
f"Starting <b>{report_type.label}</b> for @{channel}...\n\n"
|
||||
"Fetching messages...",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
async def update_status(text: str) -> None:
|
||||
try:
|
||||
await status_msg.edit_text(
|
||||
f"<b>{report_type.label}</b> for @{channel}\n\n{text}",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
messages, stats = await fetch_channel_messages(telethon_client, channel)
|
||||
except (ChannelPrivateError, ChannelInvalidError):
|
||||
await status_msg.edit_text("Channel is private or does not exist.")
|
||||
return
|
||||
except (UsernameInvalidError, UsernameNotOccupiedError):
|
||||
await status_msg.edit_text("Channel username not found.")
|
||||
return
|
||||
except FloodWaitError as e:
|
||||
await status_msg.edit_text(f"Rate limited by Telegram. Retry in {e.seconds}s.")
|
||||
return
|
||||
except Exception as e:
|
||||
log.exception("Failed to fetch channel %s", channel)
|
||||
await status_msg.edit_text(f"Failed to fetch channel: {e}")
|
||||
return
|
||||
|
||||
if not messages:
|
||||
await status_msg.edit_text("No text messages found in this channel.")
|
||||
return
|
||||
|
||||
await update_status(f"Fetched {len(messages)} messages. Chunking...")
|
||||
|
||||
chunks = chunk_messages(messages)
|
||||
await update_status(f"{len(messages)} messages in {len(chunks)} chunks. Analyzing...")
|
||||
|
||||
try:
|
||||
report = await analyze_channel(
|
||||
report_type=report_type,
|
||||
chunks=chunks,
|
||||
channel_title=stats["title"],
|
||||
channel_username=stats.get("username"),
|
||||
subscribers=stats.get("subscribers"),
|
||||
msg_count=len(messages),
|
||||
on_progress=update_status,
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception("Analysis failed for %s", channel)
|
||||
await status_msg.edit_text(f"Analysis failed: {e}")
|
||||
return
|
||||
|
||||
await update_status("Sending report...")
|
||||
|
||||
parts = split_report(report)
|
||||
for part in parts:
|
||||
try:
|
||||
await callback.message.answer(part, parse_mode="HTML")
|
||||
except Exception:
|
||||
# Fallback: send without formatting
|
||||
await callback.message.answer(part)
|
||||
|
||||
try:
|
||||
await status_msg.delete()
|
||||
except Exception:
|
||||
pass
|
||||
22
bot/handlers/start.py
Normal file
22
bot/handlers/start.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from aiogram import Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import Message
|
||||
|
||||
router = Router()
|
||||
|
||||
HELP_TEXT = (
|
||||
"<b>Telegram Channel Analyzer</b>\n\n"
|
||||
"Analyze any public Telegram channel using AI.\n\n"
|
||||
"<b>Usage:</b>\n"
|
||||
"<code>/analyze @channel</code> — Start analysis\n"
|
||||
"<code>/analyze https://t.me/channel</code> — Also works\n\n"
|
||||
"You'll choose a report type:\n"
|
||||
"• <b>Content Analysis</b> — topics, tone, themes\n"
|
||||
"• <b>Content + Stats</b> — above + engagement data\n"
|
||||
"• <b>Full Audit</b> — comprehensive review with recommendations"
|
||||
)
|
||||
|
||||
|
||||
@router.message(Command("start", "help"))
|
||||
async def cmd_start(message: Message) -> None:
|
||||
await message.answer(HELP_TEXT, parse_mode="HTML")
|
||||
Loading…
Add table
Add a link
Reference in a new issue