From f23ce3a3833c9bf6b89546ed1e95c77a23644a45 Mon Sep 17 00:00:00 2001 From: mamad Date: Thu, 27 Aug 2026 21:08:13 +0330 Subject: [PATCH] feat(ui): add inline historical scrape buttons to monitored sources cards --- services/admin_bot.py | 86 ++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 29 deletions(-) diff --git a/services/admin_bot.py b/services/admin_bot.py index a408516..52ec4f9 100644 --- a/services/admin_bot.py +++ b/services/admin_bot.py @@ -17,7 +17,7 @@ def get_main_menu_keyboard(): [Button.text("🔑 Request Login Code", resize=True), Button.text("📊 Fleet Statistics", resize=True)], [Button.text("📡 Monitored Sources", resize=True), Button.text("🎯 Target Channels", resize=True)], [Button.text("➕ Add Source Guide", resize=True), Button.text("➕ Add Target Guide", resize=True)], - [Button.text("📥 Scrape History Guide", resize=True), Button.text("❓ Help & Documentation", resize=True)] + [Button.text("❓ Help & Documentation", resize=True)] ] class AdminBotService: @@ -137,21 +137,7 @@ class AdminBotService: result = await self.collector.submit_password(pwd) await status_msg.edit(result, parse_mode="html", buttons=get_main_menu_keyboard()) - # --- History Scraper Commands --- - @self.client.on(events.NewMessage(pattern=r"(?i)^(📥 Scrape History Guide)$")) - async def cmd_scrape_guide(event: events.NewMessage.Event): - if not self.is_admin(event.sender_id): - return - guide = ( - "📥 Historical Channel Scraper:\n\n" - "Import previous/past posts from any channel:\n" - "/scrape_history <channel_id> [number_of_posts]\n\n" - "Example (Scrape last 30 posts):\n" - "/scrape_history -1001234567890 30\n\n" - "(Default is 20 posts if count is omitted)." - ) - await event.reply(guide, parse_mode="html") - + # --- Direct History Scraper Command --- @self.client.on(events.NewMessage(pattern=r"^/scrape_history\s+(-?\d+)(?:\s+(\d+))?")) async def cmd_scrape_history(event: events.NewMessage.Event): if not self.is_admin(event.sender_id): @@ -191,19 +177,31 @@ class AdminBotService: ) await event.reply(text, parse_mode="html", buttons=get_main_menu_keyboard()) - # --- Sources Management --- + # --- Sources Management with Interactive Scrape Buttons --- @self.client.on(events.NewMessage(pattern=r"(?i)^(/sources|📡 Monitored Sources)$")) async def cmd_sources(event: events.NewMessage.Event): if not self.is_admin(event.sender_id): return sources = await self.repo.get_active_sources() if not sources: - await event.reply("No active source channels configured.\nUse ➕ Add Source Guide to add one.", parse_mode="html") + await event.reply("No active source channels configured.\nTap ➕ Add Source Guide below to add one.", parse_mode="html", buttons=get_main_menu_keyboard()) return - lines = ["📡 Active Monitored Sources:\n"] + + await event.reply(f"📡 Monitored Sources ({len(sources)} Active):\nTap any button below to scrape past posts:", parse_mode="html") + for s in sources: - lines.append(f"• ID: {s.channel_id} | {s.title or 'N/A'} (@{s.username or 'none'})") - await event.reply("\n".join(lines), parse_mode="html", buttons=get_main_menu_keyboard()) + card = ( + f"📢 {s.title or 'Channel'}\n" + f"• ID: {s.channel_id}\n" + f"• Username: @{s.username or 'none'}" + ) + buttons = [ + [ + Button.inline(f"📥 Scrape 20 Posts", data=f"hist:{s.channel_id}:20"), + Button.inline(f"📥 Scrape 50 Posts", data=f"hist:{s.channel_id}:50"), + ] + ] + await event.reply(card, parse_mode="html", buttons=buttons) @self.client.on(events.NewMessage(pattern=r"(?i)^(➕ Add Source Guide)$")) async def cmd_add_source_guide(event: events.NewMessage.Event): @@ -226,7 +224,18 @@ class AdminBotService: title = event.pattern_match.group(2) username = event.pattern_match.group(3) await self.repo.add_source(channel_id=ch_id, title=title, username=username) - await event.reply(f"✅ Added source channel {title} ({ch_id}) to monitoring.", parse_mode="html", buttons=get_main_menu_keyboard()) + + buttons = [ + [ + Button.inline(f"📥 Scrape 20 Posts Now", data=f"hist:{ch_id}:20"), + Button.inline(f"📥 Scrape 50 Posts Now", data=f"hist:{ch_id}:50") + ] + ] + await event.reply( + f"✅ Added source channel {title} ({ch_id}).\n\nWould you like to scrape past posts now?", + parse_mode="html", + buttons=buttons + ) # --- Targets Management --- @self.client.on(events.NewMessage(pattern=r"(?i)^(/targets|🎯 Target Channels)$")) @@ -290,15 +299,13 @@ class AdminBotService: return help_text = ( "📖 Copykar Bot Quick Help\n\n" - "1. Authentication: Click 🔑 Request Login Code and submit via /code <12345>.\n" - "2. Monitored Sources: Add channels via /add_source.\n" - "3. Historical Posts: Backfill existing posts using /scrape_history <channel_id> [limit].\n" - "4. Review Flow: AI scans posts, checks duplicates, and sends drafts to the review channel with inline approval buttons.\n" - "5. Publishing: Approved posts are published to your target channels strictly according to their interval minutes." + "1. Monitored Sources: Tap 📡 Monitored Sources to view channels and click [📥 Scrape Posts] on any channel.\n" + "2. Review Flow: AI scans posts, checks duplicates, and sends drafts to the review channel with inline approval buttons.\n" + "3. Publishing: Approved posts are published to your target channels strictly according to their interval minutes." ) await event.reply(help_text, parse_mode="html", buttons=get_main_menu_keyboard()) - # --- Review Keyboard Callbacks --- + # --- Inline Callback Queries --- @self.client.on(events.CallbackQuery) async def on_callback(event: events.CallbackQuery.Event): if not self.is_admin(event.sender_id): @@ -306,7 +313,27 @@ class AdminBotService: return data = event.data.decode("utf-8") - if data.startswith("appr:"): + + # 1. Historical Scraping Callbacks + if data.startswith("hist:"): + _, ch_id_str, limit_str = data.split(":") + ch_id = int(ch_id_str) + limit = int(limit_str) + + if not self.collector: + await event.answer("Collector service not linked.", alert=True) + return + + await event.edit(f"⏳ Scraping the last {limit} posts from {ch_id}...", parse_mode="html", buttons=None) + + async def progress_notify(txt: str): + await event.edit(txt, parse_mode="html") + + await self.collector.scrape_channel_history(channel_id=ch_id, limit=limit, progress_callback=progress_notify) + await event.answer(f"Started scraping {limit} posts!") + + # 2. Approval Callbacks + elif data.startswith("appr:"): _, post_id_str, target_id_str = data.split(":") post_id = int(post_id_str) target_id = int(target_id_str) @@ -324,6 +351,7 @@ class AdminBotService: ) await event.answer(f"Approved for {target_title}!") + # 3. Reject Callbacks elif data.startswith("rej:"): _, post_id_str = data.split(":") post_id = int(post_id_str)