diff --git a/services/admin_bot.py b/services/admin_bot.py index cb76873..ecf2213 100644 --- a/services/admin_bot.py +++ b/services/admin_bot.py @@ -634,7 +634,7 @@ class AdminBotService: ]) return buttons - def _format_raw_post_caption(self, post: Post) -> str: + async def _format_raw_post_caption(self, post: Post) -> str: published_lines = "" if post.published_to: published_lines = "📤 وضعیت ارسال به کانال‌های مقصد:\n" @@ -654,8 +654,27 @@ class AdminBotService: tag_line = f"🏷 موضوع: #{post.subject}\n\n" if getattr(post, "subject", None) else "" + if post.source_channel_id <= -900000000: + site_id = abs(post.source_channel_id + 900000000) + site = await self.repo.get_source_website_by_id(site_id) + if site and site.name: + source_label = f"🌐 وبسایت {site.name}" + elif site and site.url: + source_label = f"🌐 وبسایت {site.url}" + else: + source_label = f"🌐 وبسایت #{site_id}" + else: + src = await self.repo.get_source_by_channel_id(post.source_channel_id) + if src and src.title: + user_part = f" (@{src.username})" if src.username else "" + source_label = f"📢 کانال {src.title}{user_part}" + elif src and src.username: + source_label = f"📢 کانال @{src.username}" + else: + source_label = f"کانال {post.source_channel_id}" + caption = ( - f"📥 پست جدید از مبدا ({post.source_channel_id}):\n\n" + f"📥 پست جدید از {source_label}:\n\n" f"{rejection_line}" f"{published_lines}" f"{tag_line}" @@ -693,7 +712,7 @@ class AdminBotService: targets = await self.repo.get_active_targets() keyboard = self._build_raw_post_keyboard(post, targets) - caption = self._format_raw_post_caption(post) + caption = await self._format_raw_post_caption(post) if refresh_only: # Update the card that already exists instead of posting a second one. @@ -1236,10 +1255,12 @@ class AdminBotService: st_icon = "🛑 متوقف (Paused)" if is_paused else "🟢 فعال و در حال کار (Running)" pause_btn_text = "▶️ راه‌اندازی سیستم" if is_paused else "🛑 توقف سیستم" pause_cb = "sys_resume" if is_paused else "sys_pause" + def_adm = await self.repo.get_default_admin_channel() + adm_label = f"{def_adm.title} ({def_adm.channel_id})" if (def_adm and def_adm.title) else (f"{self.review_channel_id}" if self.review_channel_id else "تعریف‌نشده") text = ( f"⚙️ بخش کنترل و مدیریت سیستم (System Hub):\n\n" f"• ⚡️ وضعیت کنونی سیستم: {st_icon}\n" - f"• 📋 کانال نظارت ادمین: {self.review_channel_id}\n\n" + f"• 📋 کانال نظارت ادمین: {adm_label}\n\n" f"عملیات مورد نظر را انتخاب کنید:" ) buttons = [ @@ -1274,7 +1295,9 @@ class AdminBotService: is_def = (source.admin_channel_id is None) def_mark = "✅ " if is_def else "" - buttons.append([Button.inline(f"{def_mark}⚙️ پیش‌فرض سیستم ({self.review_channel_id})", data=f"src_set_adm:{source.id}:none")]) + def_adm = await self.repo.get_default_admin_channel() + def_name = f" ({def_adm.title})" if (def_adm and def_adm.title) else (f" ({self.review_channel_id})" if self.review_channel_id else "") + buttons.append([Button.inline(f"{def_mark}⚙️ پیش‌فرض سیستم{def_name}", data=f"src_set_adm:{source.id}:none")]) buttons.append([Button.inline("➕ افزودن کانال ادمین جدید", data=f"src_add_adm:{source.id}")]) buttons.append([Button.inline("🔙 بازگشت به تنظیمات کانال مبدا", data=f"src_view:{source.id}")]) return text, buttons @@ -1299,7 +1322,9 @@ class AdminBotService: is_def = (site.admin_channel_id is None) def_mark = "✅ " if is_def else "" - buttons.append([Button.inline(f"{def_mark}⚙️ پیش‌فرض سیستم ({self.review_channel_id})", data=f"web_set_adm:{site.id}:none")]) + def_adm = await self.repo.get_default_admin_channel() + def_name = f" ({def_adm.title})" if (def_adm and def_adm.title) else (f" ({self.review_channel_id})" if self.review_channel_id else "") + buttons.append([Button.inline(f"{def_mark}⚙️ پیش‌فرض سیستم{def_name}", data=f"web_set_adm:{site.id}:none")]) buttons.append([Button.inline("➕ افزودن کانال ادمین جدید", data=f"web_add_adm:{site.id}")]) buttons.append([Button.inline("🔙 بازگشت به تنظیمات وبسایت", data=f"web_view:{site.id}")]) return text, buttons @@ -1432,7 +1457,9 @@ class AdminBotService: except Exception as e: logger.warning(f"Error deleting chunk from channel {ch_id}: {e}") - summary = f"🗑 پیام‌های کانال نظارت {ch_id} پاکسازی شدند:\n\n• 🗑 پیام‌های حذف‌شده از تلگرام: {deleted_tg_count}\n• 🗂 پست‌های بایگانی‌شده در سیستم: {cleared_db_count}" + adm = await self.repo.get_admin_channel_by_telegram_id(ch_id) + ch_name = f"«{adm.title}»" if adm and adm.title else f"{ch_id}" + summary = f"🗑 پیام‌های کانال نظارت {ch_name} پاکسازی شدند:\n\n• 🗑 پیام‌های حذف‌شده از تلگرام: {deleted_tg_count}\n• 🗂 پست‌های بایگانی‌شده در سیستم: {cleared_db_count}" return deleted_tg_count, cleared_db_count, summary @@ -1693,10 +1720,13 @@ class AdminBotService: me = await self.collector.client.get_me() userbot_status = f"🟢 آنلاین ({me.first_name})" + def_adm = await self.repo.get_default_admin_channel() + adm_label = f"{def_adm.title} ({def_adm.channel_id})" if (def_adm and def_adm.title) else (f"{self.review_channel_id}" if self.review_channel_id else "تعریف‌نشده") + welcome_text = ( "👋 به پنل مدیریت سیستم هوشمند کپی‌کار خوش آمدید!\n\n" f"• 🤖 وضعیت ربات جمع‌آوری‌کننده: {userbot_status}\n" - f"• 📋 شناسه کانال ادمین‌ها: {self.review_channel_id}\n\n" + f"• 📋 کانال نظارت ادمین: {adm_label}\n\n" "برای مدیریت کانال‌ها از دکمه‌های زیر استفاده کنید:" ) menu = await self.get_menu() @@ -3250,10 +3280,13 @@ class AdminBotService: me = await self.collector.client.get_me() userbot_status = f"🟢 آنلاین ({me.first_name})" + def_adm = await self.repo.get_default_admin_channel() + adm_label = f"{def_adm.title} ({def_adm.channel_id})" if (def_adm and def_adm.title) else (f"{self.review_channel_id}" if self.review_channel_id else "تعریف‌نشده") + welcome_text = ( "👋 منوی اصلی سیستم هوشمند کپی‌کار:\n\n" f"• 🤖 وضعیت ربات جمع‌آوری‌کننده: {userbot_status}\n" - f"• 📋 شناسه کانال ادمین‌ها: {self.review_channel_id}\n\n" + f"• 📋 کانال نظارت ادمین: {adm_label}\n\n" "برای مدیریت بخش‌های مختلف از دکمه‌های زیر استفاده کنید:" ) buttons = get_persian_inline_hub_menu() @@ -3667,7 +3700,7 @@ class AdminBotService: updated_post = await self.repo.get_post_by_id(post_id) targets = await self.repo.get_active_targets() - new_caption = self._format_raw_post_caption(updated_post) + new_caption = await self._format_raw_post_caption(updated_post) new_buttons = self._build_raw_post_keyboard(updated_post, targets) await event.edit( @@ -3689,7 +3722,7 @@ class AdminBotService: return targets = await self.repo.get_active_targets() - caption = self._format_raw_post_caption(post) + caption = await self._format_raw_post_caption(post) buttons = self._build_raw_post_keyboard(post, targets) await event.edit(caption, parse_mode="html", buttons=buttons) diff --git a/services/collector.py b/services/collector.py index 48a6f27..d553ff9 100644 --- a/services/collector.py +++ b/services/collector.py @@ -35,13 +35,14 @@ class ScrapeResult: duplicates: int = 0 error: Optional[str] = None - def summary_fa(self, channel_id: int) -> str: + def summary_fa(self, channel_id: int, channel_title: Optional[str] = None) -> str: + name_label = f"{channel_title}" if channel_title else f"{channel_id}" if self.error: - return f"❌ خطا در دریافت پست‌های کانال {channel_id}: {self.error}" + return f"❌ خطا در دریافت پست‌های کانال {name_label}: {self.error}" header = ( - f"✅ {self.collected} پست جدید از {channel_id} دریافت و به کانال ادمین ارسال شد." + f"✅ {self.collected} پست جدید از کانال {name_label} دریافت و به کانال ادمین ارسال شد." if self.collected - else f"ℹ️ هیچ پست جدیدی در {channel_id} پیدا نشد." + else f"ℹ️ هیچ پست جدیدی در کانال {name_label} پیدا نشد." ) return ( f"{header}\n\n" @@ -388,13 +389,13 @@ class CollectorService: result.already_stored += 1 if progress_callback: - await progress_callback(result.summary_fa(channel_id)) + await progress_callback(result.summary_fa(channel_id, source_title)) return result except Exception as e: await log_exception("collector.scrape_history", e, {"channel_id": channel_id, "limit": limit}) result.error = str(e) if progress_callback: - await progress_callback(result.summary_fa(channel_id)) + await progress_callback(result.summary_fa(channel_id, source_title)) return result async def stop(self): diff --git a/tests/test_name_formatting.py b/tests/test_name_formatting.py new file mode 100644 index 0000000..5fa5dc4 --- /dev/null +++ b/tests/test_name_formatting.py @@ -0,0 +1,45 @@ +import pytest +from unittest.mock import AsyncMock, MagicMock +from services.collector import ScrapeResult +from services.admin_bot import AdminBotService +from db.models import Post, SourceChannel, SourceWebsite + +def test_collector_summary_fa_with_title(): + res = ScrapeResult(scanned=10, collected=5, already_stored=3, duplicates=2) + summary = res.summary_fa(channel_id=-100123456, channel_title="کانال خبری فناوری") + assert "کانال خبری فناوری" in summary + assert "-100123456" not in summary + +def test_collector_summary_fa_fallback_to_id(): + res = ScrapeResult(scanned=10, collected=0, already_stored=10, duplicates=0) + summary = res.summary_fa(channel_id=-100123456, channel_title=None) + assert "-100123456" in summary + +@pytest.mark.asyncio +async def test_admin_bot_format_raw_post_caption_telegram_channel(): + repo = MagicMock() + repo.get_source_by_channel_id = AsyncMock(return_value=SourceChannel( + id=1, channel_id=-100123456, username="tech_news", title="اخبار روز تکنولوژی" + )) + bot = AdminBotService(repo=repo, ai_processor=MagicMock(), queue=MagicMock()) + + post = Post(id=1, source_channel_id=-100123456, source_message_id=10, raw_text="متن تست پست") + caption = await bot._format_raw_post_caption(post) + + assert "اخبار روز تکنولوژی" in caption + assert "@tech_news" in caption + +@pytest.mark.asyncio +async def test_admin_bot_format_raw_post_caption_website(): + repo = MagicMock() + pseudo_id = -900000005 + repo.get_source_website_by_id = AsyncMock(return_value=SourceWebsite( + id=5, name="دیجیاتو", url="https://digiato.com" + )) + bot = AdminBotService(repo=repo, ai_processor=MagicMock(), queue=MagicMock()) + + post = Post(id=2, source_channel_id=pseudo_id, source_message_id=1, raw_text="خبر جدید وبسایت") + caption = await bot._format_raw_post_caption(post) + + assert "دیجیاتو" in caption + assert "وبسایت" in caption