feat: organize bot menu into Copy, AI, Bots, System, Monitor categories
This commit is contained in:
+221
-18
@@ -42,15 +42,10 @@ def clamp_for_telegram(text: str, has_media: bool) -> str:
|
||||
|
||||
|
||||
def get_persian_main_menu(is_paused: bool = False):
|
||||
pause_btn = "▶️ راهاندازی سیستم" if is_paused else "🛑 توقف سیستم"
|
||||
return [
|
||||
[Button.text("🏠 منوی اصلی", resize=True), Button.text("📊 آمار ناوگان", resize=True)],
|
||||
[Button.text("📡 کانالهای مبدا", resize=True), Button.text("🎯 کانالهای مقصد", resize=True)],
|
||||
[Button.text("🌐 وبسایتهای مبدا", resize=True), Button.text("📂 دستهبندیها", resize=True)],
|
||||
[Button.text("🧠 تنظیمات AI", resize=True), Button.text("⚠️ خطاهای سیستم", resize=True)],
|
||||
[Button.text("➕ افزودن مبدا / مقصد", resize=True), Button.text("📨 پستهای بررسینشده", resize=True)],
|
||||
[Button.text("🔑 کد لاگین", resize=True), Button.text(pause_btn, resize=True)],
|
||||
[Button.text("❓ راهنمای سیستم", resize=True)]
|
||||
[Button.text("📋 Copy (محتوا)", resize=True), Button.text("🧠 AI (هوش مصنوعی)", resize=True)],
|
||||
[Button.text("🤖 Bots (کانالها و رباتها)", resize=True), Button.text("📊 Monitor (مانیتورینگ)", resize=True)],
|
||||
[Button.text("⚙️ System (سیستم)", resize=True), Button.text("🏠 منوی اصلی", resize=True)]
|
||||
]
|
||||
|
||||
|
||||
@@ -1060,6 +1055,84 @@ class AdminBotService:
|
||||
return text, buttons
|
||||
|
||||
|
||||
async def _render_copy_hub(self) -> Tuple[str, List[List[Button]]]:
|
||||
unreviewed = await self.repo.get_unreviewed_posts(limit=50)
|
||||
text = (
|
||||
"📋 <b>بخش کپی و محتوا (Copy Hub):</b>\n\n"
|
||||
f"• 📨 <b>پستهای منتظر بررسی:</b> {len(unreviewed)} پست\n\n"
|
||||
"از گزینههای زیر برای مدیریت جریان محتوا و انتشار استفاده کنید:"
|
||||
)
|
||||
buttons = [
|
||||
[Button.inline(f"📨 ارسال پستهای بررسینشده ({len(unreviewed)})", data="hub_pending")],
|
||||
[Button.inline("➕ افزودن مبدا / مقصد جدید", data="hub_add")],
|
||||
[Button.inline("📂 دستهبندی موضوعی کانالها", data="hub_cats")],
|
||||
[Button.inline("🔙 بازگشت به منوی اصلی", data="back_to_main")]
|
||||
]
|
||||
return text, buttons
|
||||
|
||||
async def _render_bots_hub(self) -> Tuple[str, List[List[Button]]]:
|
||||
sources = await self.repo.get_active_sources()
|
||||
targets = await self.repo.get_active_targets()
|
||||
websites = await self.repo.get_active_source_websites()
|
||||
text = (
|
||||
f"🤖 <b>بخش کانالها و رباتها (Bots Hub):</b>\n\n"
|
||||
f"• 📡 <b>کانالهای مبدا:</b> {len(sources)} کانال\n"
|
||||
f"• 🎯 <b>کانالهای مقصد:</b> {len(targets)} کانال\n"
|
||||
f"• 🌐 <b>وبسایتهای مبدا:</b> {len(websites)} وبسایت\n\n"
|
||||
f"<i>بخش مورد نظر را برای مشاهده و مدیریت انتخاب کنید:</i>"
|
||||
)
|
||||
buttons = [
|
||||
[
|
||||
Button.inline(f"📡 کانالهای مبدا ({len(sources)})", data="list_src"),
|
||||
Button.inline(f"🎯 کانالهای مقصد ({len(targets)})", data="list_trg")
|
||||
],
|
||||
[
|
||||
Button.inline(f"🌐 وبسایتهای مبدا ({len(websites)})", data="list_web"),
|
||||
Button.inline("🔑 کد لاگین یوزربات", data="hub_req_code")
|
||||
],
|
||||
[Button.inline("🔙 بازگشت به منوی اصلی", data="back_to_main")]
|
||||
]
|
||||
return text, buttons
|
||||
|
||||
async def _render_system_hub(self) -> Tuple[str, List[List[Button]]]:
|
||||
is_paused = await self.repo.is_system_paused()
|
||||
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"
|
||||
text = (
|
||||
f"⚙️ <b>بخش کنترل و مدیریت سیستم (System Hub):</b>\n\n"
|
||||
f"• ⚡️ <b>وضعیت کنونی سیستم:</b> <b>{st_icon}</b>\n"
|
||||
f"• 📋 <b>کانال نظارت ادمین:</b> <code>{self.review_channel_id}</code>\n\n"
|
||||
f"<i>عملیات مورد نظر را انتخاب کنید:</i>"
|
||||
)
|
||||
buttons = [
|
||||
[Button.inline(pause_btn_text, data=pause_cb)],
|
||||
[
|
||||
Button.inline("📝 گزارش تغییرات (Release Notes)", data="sys_changes"),
|
||||
Button.inline("❓ راهنمای سیستم", data="sys_help")
|
||||
],
|
||||
[Button.inline("🔙 بازگشت به منوی اصلی", data="back_to_main")]
|
||||
]
|
||||
return text, buttons
|
||||
|
||||
async def _render_monitor_hub(self) -> Tuple[str, List[List[Button]]]:
|
||||
rep = await get_instant_metrics_report()
|
||||
buttons = [
|
||||
[
|
||||
Button.inline("🔄 بروزرسانی آمار", data="rep_refresh"),
|
||||
Button.inline("⚠️ بررسی خطاهای سیستم", data="hub_errors")
|
||||
],
|
||||
[
|
||||
Button.inline("⏱ نمودار ۱۵ دقیقه", data="rep_gr:15m"),
|
||||
Button.inline("⏱ نمودار ۳ ساعت", data="rep_gr:3h")
|
||||
],
|
||||
[
|
||||
Button.inline("⏱ نمودار ۲۴ ساعت", data="rep_gr:24h"),
|
||||
Button.inline("🔙 بازگشت به منوی اصلی", data="back_to_main")
|
||||
]
|
||||
]
|
||||
return rep, buttons
|
||||
|
||||
async def _render_auto_sources(self, target_id: int):
|
||||
"""Toggle screen listing every source with its on/off state for this target."""
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
@@ -1291,6 +1364,42 @@ class AdminBotService:
|
||||
menu = await self.get_menu()
|
||||
await event.reply(welcome_text, parse_mode="html", buttons=menu)
|
||||
|
||||
# --- 5 Primary Category Hubs: Copy, AI, Bots, System, Monitor ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/copy|📋 copy \(محتوا\)|📋 copy|copy)$"))
|
||||
async def cmd_copy_hub(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
text, buttons = await self._render_copy_hub()
|
||||
await event.reply(text, parse_mode="html", buttons=buttons)
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/ai|🧠 ai \(هوش مصنوعی\)|🧠 ai|ai)$"))
|
||||
async def cmd_ai_hub(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
text, buttons = await self._render_ai_menu()
|
||||
await event.reply(text, parse_mode="html", buttons=buttons)
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/bots|🤖 bots \(کانالها و رباتها\)|🤖 bots|bots)$"))
|
||||
async def cmd_bots_hub(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
text, buttons = await self._render_bots_hub()
|
||||
await event.reply(text, parse_mode="html", buttons=buttons)
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/system|⚙️ system \(سیستم\)|⚙️ system|system)$"))
|
||||
async def cmd_system_hub(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
text, buttons = await self._render_system_hub()
|
||||
await event.reply(text, parse_mode="html", buttons=buttons)
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/monitor|📊 monitor \(مانیتورینگ\)|📊 monitor|monitor)$"))
|
||||
async def cmd_monitor_hub(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
text, buttons = await self._render_monitor_hub()
|
||||
await event.reply(text, parse_mode="html", buttons=buttons)
|
||||
|
||||
# --- Statistics & Grafana Metrics Report ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/stats|/report|📊 آمار ناوگان|📊 آمار و وضعیت ناوگان|📊 گزارش و آمار سیستم)$"))
|
||||
async def cmd_stats(event: events.NewMessage.Event):
|
||||
@@ -1309,15 +1418,6 @@ class AdminBotService:
|
||||
]
|
||||
await event.reply(report_text, parse_mode="html", buttons=buttons)
|
||||
|
||||
|
||||
# --- AI Settings & Logs ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/ai|🧠 تنظیمات AI|🧠 تنظیمات و لاگهای AI)$"))
|
||||
async def cmd_ai(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
text, buttons = await self._render_ai_menu()
|
||||
await event.reply(text, parse_mode="html", buttons=buttons)
|
||||
|
||||
# --- System Errors ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/errors|/err|⚠️ خطاهای سیستم)$"))
|
||||
async def cmd_errors(event: events.NewMessage.Event):
|
||||
@@ -1583,13 +1683,17 @@ class AdminBotService:
|
||||
|
||||
# Ignore main menu commands
|
||||
if text in ["/start", "/menu", "منو", "منوی اصلی", "🏠 منوی اصلی",
|
||||
"/copy", "📋 copy (محتوا)", "📋 Copy (محتوا)", "📋 کپی", "Copy",
|
||||
"/ai", "🧠 ai (هوش مصنوعی)", "🧠 AI (هوش مصنوعی)", "🧠 تنظیمات AI", "🧠 تنظیمات و لاگهای AI", "AI",
|
||||
"/bots", "🤖 bots (کانالها و رباتها)", "🤖 Bots (کانالها و رباتها)", "🤖 کانالها و رباتها", "Bots",
|
||||
"/system", "⚙️ system (سیستم)", "⚙️ System (سیستم)", "⚙️ مدیریت سیستم", "System",
|
||||
"/monitor", "📊 monitor (مانیتورینگ)", "📊 Monitor (مانیتورینگ)", "📊 مانیتورینگ", "Monitor",
|
||||
"/stats", "/report", "📊 آمار ناوگان", "📊 آمار و وضعیت ناوگان", "📊 گزارش و آمار سیستم",
|
||||
"/sources", "📡 کانالهای مبدا", "/targets", "🎯 کانالهای مقصد",
|
||||
"/websites", "/sites", "🌐 وبسایتهای مبدا",
|
||||
"/request_code", "🔑 کد لاگین", "🔑 درخواست کد لاگین",
|
||||
"/pending", "📨 پستهای بررسینشده", "📨 ارسال پستهای بررسینشده",
|
||||
"/errors", "/err", "⚠️ خطاهای سیستم",
|
||||
"/ai", "🧠 تنظیمات AI", "🧠 تنظیمات و لاگهای AI",
|
||||
"/categories", "/cats", "📂 دستهبندیها", "📂 دستهبندی کانالها",
|
||||
"/add", "➕ افزودن مبدا / مقصد", "➕ افزودن کانال مبدا", "➕ افزودن کانال مقصد",
|
||||
"/pause", "/stop", "🛑 توقف سیستم", "🛑 توقف اضطراری سیستم",
|
||||
@@ -2697,6 +2801,105 @@ class AdminBotService:
|
||||
await event.reply(guide, parse_mode="html", buttons=get_cancel_button())
|
||||
await event.answer()
|
||||
|
||||
elif data == "hub_add":
|
||||
text = "➕ <b>افزودن منبع یا کانال جدید:</b>\n\nلطفاً نوع مورد نظر را انتخاب کنید:"
|
||||
buttons = [
|
||||
[Button.inline("📡 افزودن کانال تلگرام مبدا", data="hub_add_src")],
|
||||
[Button.inline("🎯 افزودن کانال تلگرام مقصد", data="hub_add_trg")],
|
||||
[Button.inline("🌐 افزودن وبسایت مبدا (Web)", data="add_web")],
|
||||
[Button.inline("🔙 بازگشت به منوی کپی", data="hub_copy_back")]
|
||||
]
|
||||
await event.edit(text, parse_mode="html", buttons=buttons)
|
||||
await event.answer()
|
||||
|
||||
elif data == "hub_copy_back":
|
||||
text, buttons = await self._render_copy_hub()
|
||||
await event.edit(text, parse_mode="html", buttons=buttons)
|
||||
await event.answer()
|
||||
|
||||
elif data == "hub_pending":
|
||||
posts = await self.repo.get_unreviewed_posts(limit=50)
|
||||
if not posts:
|
||||
await event.answer("✅ هیچ پست بررسینشدهای در صف نیست.", alert=True)
|
||||
return
|
||||
for post in posts:
|
||||
await self.send_raw_review_post(post.id)
|
||||
await event.answer(f"✅ تعداد {len(posts)} پست بررسینشده به کانال ادمین ارسال شد.", alert=True)
|
||||
|
||||
elif data == "hub_cats":
|
||||
text, buttons = await self._render_categories_menu()
|
||||
await event.edit(text, parse_mode="html", buttons=buttons or None)
|
||||
await event.answer()
|
||||
|
||||
elif data == "hub_req_code":
|
||||
if not self.collector:
|
||||
await event.answer("سرویس کالکتور متصل نیست.", alert=True)
|
||||
return
|
||||
try:
|
||||
sent = await self.collector.client.send_code_request(self.collector.phone)
|
||||
self.collector.phone_code_hash = sent.phone_code_hash
|
||||
self.user_states[event.sender_id] = {"action": "wait_login_code"}
|
||||
await event.reply(
|
||||
f"📩 <b>کد تایید ارسال شد!</b>\n\n"
|
||||
f"لطفا کد دریافت شده به شماره <code>{self.collector.phone}</code> را ارسال کنید:\n"
|
||||
f"<i>(یا به فرمت /code 12345)</i>",
|
||||
parse_mode="html",
|
||||
buttons=get_cancel_button()
|
||||
)
|
||||
await event.answer("کد ارسال شد.")
|
||||
except Exception as e:
|
||||
await event.answer(f"خطا در ارسال کد: {e}", alert=True)
|
||||
|
||||
elif data == "hub_errors":
|
||||
text, buttons = await self._render_error_report()
|
||||
await event.edit(text, parse_mode="html", buttons=buttons or None)
|
||||
await event.answer()
|
||||
|
||||
elif data == "sys_pause":
|
||||
await self.repo.set_system_paused(True)
|
||||
text, buttons = await self._render_system_hub()
|
||||
await event.edit(text, parse_mode="html", buttons=buttons)
|
||||
await event.answer("🛑 سیستم متوقف شد.", alert=True)
|
||||
|
||||
elif data == "sys_resume":
|
||||
await self.repo.set_system_paused(False)
|
||||
text, buttons = await self._render_system_hub()
|
||||
await event.edit(text, parse_mode="html", buttons=buttons)
|
||||
await event.answer("▶️ سیستم مجدداً فعال شد.", alert=True)
|
||||
|
||||
elif data == "sys_changes":
|
||||
notes = self.get_latest_change_notes()
|
||||
await event.reply(notes, parse_mode="html")
|
||||
await event.answer()
|
||||
|
||||
elif data == "sys_help":
|
||||
help_text = (
|
||||
"📖 <b>راهنمای جامع سیستم کپیکار:</b>\n\n"
|
||||
"• 📋 <b>کپی (Copy):</b> مدیریت پستهای بررسینشده، دستهبندیها و اضافه کردن منابع.\n"
|
||||
"• 🧠 <b>هوش مصنوعی (AI):</b> تنظیم ارائهدهندهها، زنجیره پشتیبان (Fallback)، Vision و پرامپتها.\n"
|
||||
"• 🤖 <b>رباتها (Bots):</b> کانالهای مبدا، کانالهای مقصد، وبسایتهای مبدا و لاگین یوزربات.\n"
|
||||
"• 📊 <b>مانیتورینگ (Monitor):</b> آمار لحظهای، صفها، نرخ پردازش و لاگ خطاهای سیستم.\n"
|
||||
"• ⚙️ <b>سیستم (System):</b> توقف اضطراری، راهاندازی مجدد، گزارش تغییرات و راهنما."
|
||||
)
|
||||
await event.reply(help_text, parse_mode="html")
|
||||
await event.answer()
|
||||
|
||||
elif data == "back_to_main":
|
||||
userbot_status = "🔴 قطع / نیاز به لاگین"
|
||||
if self.collector and self.collector.client.is_connected() and await self.collector.client.is_user_authorized():
|
||||
me = await self.collector.client.get_me()
|
||||
userbot_status = f"🟢 آنلاین ({me.first_name})"
|
||||
|
||||
welcome_text = (
|
||||
"👋 <b>منوی اصلی سیستم هوشمند کپیکار:</b>\n\n"
|
||||
f"• 🤖 <b>وضعیت ربات جمعآوریکننده:</b> {userbot_status}\n"
|
||||
f"• 📋 <b>شناسه کانال ادمینها:</b> <code>{self.review_channel_id}</code>\n\n"
|
||||
"برای مدیریت بخشهای مختلف از دکمههای زیر استفاده کنید:"
|
||||
)
|
||||
menu = await self.get_menu()
|
||||
await event.edit(welcome_text, parse_mode="html", buttons=menu)
|
||||
await event.answer()
|
||||
|
||||
elif data.startswith("auto_src:"):
|
||||
target_id = int(data.split(":")[1])
|
||||
text, buttons = await self._render_auto_sources(target_id)
|
||||
|
||||
@@ -5,16 +5,18 @@ from core.llm import LLMClient
|
||||
from services.admin_bot import get_persian_main_menu, AdminBotService
|
||||
|
||||
|
||||
def test_main_menu_pause_toggle():
|
||||
menu_running = get_persian_main_menu(is_paused=False)
|
||||
button_texts_running = [getattr(b.button, 'text', '') for row in menu_running for b in row]
|
||||
assert any("🛑 توقف" in t for t in button_texts_running)
|
||||
assert not any("▶️ راهاندازی" in t for t in button_texts_running)
|
||||
async def test_system_hub_pause_toggle():
|
||||
repo = AsyncMock()
|
||||
repo.is_system_paused.return_value = False
|
||||
admin_bot = AdminBotService(repo=repo, ai_processor=None, queue=None, review_channel_id=-100)
|
||||
text, buttons = await admin_bot._render_system_hub()
|
||||
btn_texts = [getattr(b, 'text', '') for row in buttons for b in row]
|
||||
assert any("🛑 توقف" in t for t in btn_texts)
|
||||
|
||||
menu_paused = get_persian_main_menu(is_paused=True)
|
||||
button_texts_paused = [getattr(b.button, 'text', '') for row in menu_paused for b in row]
|
||||
assert any("▶️ راهاندازی" in t for t in button_texts_paused)
|
||||
assert not any("🛑 توقف" in t for t in button_texts_paused)
|
||||
repo.is_system_paused.return_value = True
|
||||
text, buttons = await admin_bot._render_system_hub()
|
||||
btn_texts = [getattr(b, 'text', '') for row in buttons for b in row]
|
||||
assert any("▶️ راهاندازی" in t for t in btn_texts)
|
||||
|
||||
|
||||
async def test_llm_fallback_chain_success_on_fallback():
|
||||
@@ -137,7 +139,7 @@ async def test_llm_fallback_chain_all_fail_alert():
|
||||
|
||||
|
||||
async def main():
|
||||
test_main_menu_pause_toggle()
|
||||
await test_system_hub_pause_toggle()
|
||||
await test_llm_fallback_chain_success_on_fallback()
|
||||
await test_llm_fallback_chain_all_fail_alert()
|
||||
print("All system stop and multi-provider AI fallback chain tests passed successfully!")
|
||||
|
||||
@@ -112,11 +112,12 @@ async def test_website_custom_needs_and_prompt_injection():
|
||||
|
||||
async def test_menu_layout():
|
||||
menu = get_persian_main_menu(is_paused=False)
|
||||
assert len(menu) == 7
|
||||
first_row_texts = [getattr(getattr(b, "button", None), "text", str(b)) for b in menu[0]]
|
||||
assert any("🏠 منوی اصلی" in t for t in first_row_texts)
|
||||
fifth_row_texts = [getattr(getattr(b, "button", None), "text", str(b)) for b in menu[4]]
|
||||
assert any("➕ افزودن مبدا / مقصد" in t for t in fifth_row_texts)
|
||||
all_texts = [getattr(getattr(b, "button", None), "text", str(b)) for row in menu for b in row]
|
||||
assert any("Copy" in t for t in all_texts)
|
||||
assert any("AI" in t for t in all_texts)
|
||||
assert any("Bots" in t for t in all_texts)
|
||||
assert any("Monitor" in t for t in all_texts)
|
||||
assert any("System" in t for t in all_texts)
|
||||
|
||||
|
||||
async def main():
|
||||
|
||||
Reference in New Issue
Block a user