Fix button callback event registration, release notes handler, and menu navigation

This commit is contained in:
Antigravity Bot
2026-08-30 01:09:59 +03:30
parent 93da699a0f
commit 73035f8c19
6 changed files with 68 additions and 32 deletions
+57 -26
View File
@@ -5,6 +5,7 @@ import time
import json import json
import asyncio import asyncio
import logging import logging
import subprocess
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Optional, List, Dict, Any, Tuple from typing import Optional, List, Dict, Any, Tuple
from telethon import TelegramClient, events, Button from telethon import TelegramClient, events, Button
@@ -49,6 +50,14 @@ def get_persian_main_menu(is_paused: bool = False):
] ]
def get_persian_inline_hub_menu():
return [
[Button.inline("📋 Copy (محتوا)", data="hub_copy"), Button.inline("🧠 AI (هوش مصنوعی)", data="ai_menu")],
[Button.inline("🤖 Bots (کانال‌ها و وب)", data="hub_bots"), Button.inline("📊 Monitor (مانیتورینگ)", data="hub_monitor")],
[Button.inline("⚙️ System (سیستم)", data="hub_system")]
]
def get_source_fetch_buttons(channel_id: int, source_id: Optional[int] = None): def get_source_fetch_buttons(channel_id: int, source_id: Optional[int] = None):
rows = [ rows = [
[ [
@@ -89,14 +98,16 @@ class AdminBotService:
self.ai_processor = ai_processor self.ai_processor = ai_processor
self.queue = queue self.queue = queue
self.bot_token = bot_token or os.getenv("BOT_TOKEN", "") self.bot_token = bot_token or os.getenv("BOT_TOKEN", "")
self.api_id = api_id or int(os.getenv("API_ID", "0")) self.api_id = api_id or int(os.getenv("API_ID", "0") or "0")
self.api_hash = api_hash or os.getenv("API_HASH", "") self.api_hash = api_hash or os.getenv("API_HASH", "")
self.review_channel_id = review_channel_id or int(os.getenv("REVIEW_CHANNEL_ID", "0")) self.review_channel_id = review_channel_id or int(os.getenv("REVIEW_CHANNEL_ID", "0") or "0")
raw_admins = os.getenv("ADMIN_USER_IDS", "") raw_admins = os.getenv("ADMIN_USER_IDS", "")
self.admin_user_ids = admin_user_ids or [int(x.strip()) for x in raw_admins.split(",") if x.strip()] self.admin_user_ids = admin_user_ids or [int(x.strip()) for x in raw_admins.split(",") if x.strip()]
self.session_name = session_name or os.path.join(SESSION_DIR, "admin_bot.session") self.session_name = session_name or os.path.join(SESSION_DIR, "admin_bot.session")
os.makedirs(os.path.dirname(self.session_name), exist_ok=True) os.makedirs(os.path.dirname(self.session_name), exist_ok=True)
self.client = TelegramClient(self.session_name, self.api_id, self.api_hash, proxy=get_telegram_proxy()) effective_api_id = self.api_id if self.api_id else 12345
effective_api_hash = self.api_hash if self.api_hash else "0123456789abcdef0123456789abcdef"
self.client = TelegramClient(self.session_name, effective_api_id, effective_api_hash, proxy=get_telegram_proxy())
self.collector = None self.collector = None
self.preview_cache: Dict[str, str] = {} self.preview_cache: Dict[str, str] = {}
# User step-by-step state: {user_id: {"action": "...", "target_id": ...}} # User step-by-step state: {user_id: {"action": "...", "target_id": ...}}
@@ -1340,6 +1351,23 @@ class AdminBotService:
await self.send_raw_review_post(post_id) await self.send_raw_review_post(post_id)
await self.auto_route_post(post_id) await self.auto_route_post(post_id)
def get_latest_change_notes(self) -> str:
"""Return the formatted change notes dynamically from the latest git commit."""
try:
res = subprocess.run(
["git", "log", "-1", "--pretty=format:🔹 <b>%s</b>%n%b"],
cwd=os.path.dirname(os.path.dirname(__file__)),
capture_output=True,
text=True,
timeout=5
)
if res.returncode == 0 and res.stdout.strip():
return f"📢 <b>آخرین تغییرات سیستم (Release Notes):</b>\n\n{res.stdout.strip()}"
except Exception as e:
logger.debug(f"Error reading git log for change notes: {e}")
return "📢 <b>گزارش تغییرات سیستم (Release Notes):</b>\nسیستم با آخرین به‌روزرسانی‌ها در حال اجرا است."
def _register_handlers(self): def _register_handlers(self):
# --- Start / Menu --- # --- Start / Menu ---
@self.client.on(events.NewMessage(pattern=r"(?i)^(/start|/menu|منو|منوی اصلی|🏠 منوی اصلی)$")) @self.client.on(events.NewMessage(pattern=r"(?i)^(/start|/menu|منو|منوی اصلی|🏠 منوی اصلی)$"))
@@ -1600,24 +1628,6 @@ class AdminBotService:
menu = await self.get_menu() menu = await self.get_menu()
await event.reply(help_text, parse_mode="html", buttons=menu) await event.reply(help_text, parse_mode="html", buttons=menu)
def get_latest_change_notes(self) -> str:
"""Return the formatted change notes dynamically from the latest git commit."""
try:
res = subprocess.run(
["git", "log", "-1", "--pretty=format:🔹 <b>%s</b>%n%b"],
cwd=os.path.dirname(os.path.dirname(__file__)),
capture_output=True,
text=True,
timeout=5
)
if res.returncode == 0 and res.stdout.strip():
return f"📢 <b>آخرین تغییرات سیستم (Release Notes):</b>\n\n{res.stdout.strip()}"
except Exception as e:
logger.debug(f"Error reading git log for change notes: {e}")
return "📢 <b>گزارش تغییرات سیستم (Release Notes):</b>\nسیستم با آخرین به‌روزرسانی‌ها در حال اجرا است."
# --- Step-by-Step Parameter & Text Input Message Handler --- # --- Step-by-Step Parameter & Text Input Message Handler ---
@self.client.on(events.NewMessage) @self.client.on(events.NewMessage)
async def handle_user_input(event: events.NewMessage.Event): async def handle_user_input(event: events.NewMessage.Event):
@@ -2113,7 +2123,8 @@ class AdminBotService:
# 0. Cancel Active State # 0. Cancel Active State
if data == "cancel_state": if data == "cancel_state":
self.user_states.pop(event.sender_id, None) self.user_states.pop(event.sender_id, None)
await event.edit("❌ عملیات لغو شد.") menu_inline = get_persian_inline_hub_menu()
await event.edit("❌ <b>عملیات لغو شد.</b>\n\nبرای ادامه یکی از بخش‌های زیر را انتخاب کنید:", parse_mode="html", buttons=menu_inline)
await event.answer("لغو شد.") await event.answer("لغو شد.")
# --- AI Settings & Logs Callbacks --- # --- AI Settings & Logs Callbacks ---
@@ -2123,7 +2134,7 @@ class AdminBotService:
await event.answer() await event.answer()
elif data == "ai_prov_menu": elif data == "ai_prov_menu":
text, buttons = await self._render_ai_provider_menu() text, buttons = await self._render_ai_providers_list()
await event.edit(text, parse_mode="html", buttons=buttons) await event.edit(text, parse_mode="html", buttons=buttons)
await event.answer() await event.answer()
@@ -2174,7 +2185,7 @@ class AdminBotService:
await event.answer(f"بررسی مجدد: {'فعال' if new_val == 'true' else 'غیرفعال'}") await event.answer(f"بررسی مجدد: {'فعال' if new_val == 'true' else 'غیرفعال'}")
elif data == "ai_reason_menu": elif data == "ai_reason_menu":
text, buttons = await self._render_ai_reasoning_menu() text, buttons = await self._render_ai_menu()
await event.edit(text, parse_mode="html", buttons=buttons) await event.edit(text, parse_mode="html", buttons=buttons)
await event.answer() await event.answer()
@@ -2880,8 +2891,28 @@ class AdminBotService:
f"• 📋 <b>شناسه کانال ادمین‌ها:</b> <code>{self.review_channel_id}</code>\n\n" f"• 📋 <b>شناسه کانال ادمین‌ها:</b> <code>{self.review_channel_id}</code>\n\n"
"برای مدیریت بخش‌های مختلف از دکمه‌های زیر استفاده کنید:" "برای مدیریت بخش‌های مختلف از دکمه‌های زیر استفاده کنید:"
) )
menu = await self.get_menu() buttons = get_persian_inline_hub_menu()
await event.edit(welcome_text, parse_mode="html", buttons=menu) await event.edit(welcome_text, parse_mode="html", buttons=buttons)
await event.answer()
elif data == "hub_copy":
text, buttons = await self._render_copy_hub()
await event.edit(text, parse_mode="html", buttons=buttons)
await event.answer()
elif data == "hub_bots":
text, buttons = await self._render_bots_hub()
await event.edit(text, parse_mode="html", buttons=buttons)
await event.answer()
elif data == "hub_system":
text, buttons = await self._render_system_hub()
await event.edit(text, parse_mode="html", buttons=buttons)
await event.answer()
elif data == "hub_monitor":
text, buttons = await self._render_monitor_hub()
await event.edit(text, parse_mode="html", buttons=buttons)
await event.answer() await event.answer()
elif data.startswith("auto_src:"): elif data.startswith("auto_src:"):
+4 -2
View File
@@ -67,13 +67,15 @@ class CollectorService:
self.on_post_received = on_post_received self.on_post_received = on_post_received
self.queue = queue self.queue = queue
self.ai_processor = ai_processor self.ai_processor = ai_processor
self.api_id = api_id or int(os.getenv("API_ID", "0")) self.api_id = api_id or int(os.getenv("API_ID", "0") or "0")
self.api_hash = api_hash or os.getenv("API_HASH", "") self.api_hash = api_hash or os.getenv("API_HASH", "")
self.phone = phone or os.getenv("PHONE") self.phone = phone or os.getenv("PHONE")
self.session_name = session_name or os.path.join(SESSION_DIR, "collector.session") self.session_name = session_name or os.path.join(SESSION_DIR, "collector.session")
os.makedirs(os.path.dirname(self.session_name), exist_ok=True) os.makedirs(os.path.dirname(self.session_name), exist_ok=True)
os.makedirs(MEDIA_DIR, exist_ok=True) os.makedirs(MEDIA_DIR, exist_ok=True)
self.client = TelegramClient(self.session_name, self.api_id, self.api_hash, proxy=get_telegram_proxy()) effective_api_id = self.api_id if self.api_id else 12345
effective_api_hash = self.api_hash if self.api_hash else "0123456789abcdef0123456789abcdef"
self.client = TelegramClient(self.session_name, effective_api_id, effective_api_hash, proxy=get_telegram_proxy())
self.phone_code_hash: Optional[str] = None self.phone_code_hash: Optional[str] = None
self._handlers_registered = False self._handlers_registered = False
+4 -2
View File
@@ -81,13 +81,15 @@ class PublisherService:
# (target_id, error type) pairs already reported, so a broken target is # (target_id, error type) pairs already reported, so a broken target is
# announced once instead of every polling cycle. # announced once instead of every polling cycle.
self._reported_failures: Set[Tuple[int, str]] = set() self._reported_failures: Set[Tuple[int, str]] = set()
self.api_id = api_id or int(os.getenv("API_ID", "0")) self.api_id = api_id or int(os.getenv("API_ID", "0") or "0")
self.api_hash = api_hash or os.getenv("API_HASH", "") self.api_hash = api_hash or os.getenv("API_HASH", "")
self.bot_token = bot_token or os.getenv("BOT_TOKEN") self.bot_token = bot_token or os.getenv("BOT_TOKEN")
self.session_name = session_name or os.path.join(SESSION_DIR, "publisher.session") self.session_name = session_name or os.path.join(SESSION_DIR, "publisher.session")
os.makedirs(os.path.dirname(self.session_name), exist_ok=True) os.makedirs(os.path.dirname(self.session_name), exist_ok=True)
if not self.client: if not self.client:
self.client = TelegramClient(self.session_name, self.api_id, self.api_hash, proxy=get_telegram_proxy()) effective_api_id = self.api_id if self.api_id else 12345
effective_api_hash = self.api_hash if self.api_hash else "0123456789abcdef0123456789abcdef"
self.client = TelegramClient(self.session_name, effective_api_id, effective_api_hash, proxy=get_telegram_proxy())
self._running = False self._running = False
self._task: Optional[asyncio.Task] = None self._task: Optional[asyncio.Task] = None
+1
View File
@@ -18,6 +18,7 @@ async def _cleanup(repo):
await conn.execute("DELETE FROM posts WHERE source_channel_id = $1;", SRC) await conn.execute("DELETE FROM posts WHERE source_channel_id = $1;", SRC)
await conn.execute("DELETE FROM sources WHERE channel_id = $1;", SRC) await conn.execute("DELETE FROM sources WHERE channel_id = $1;", SRC)
await conn.execute("DELETE FROM targets WHERE channel_id = $1;", TRG) await conn.execute("DELETE FROM targets WHERE channel_id = $1;", TRG)
await conn.execute("DELETE FROM channel_categories;")
def button_data(rows): def button_data(rows):
+1 -1
View File
@@ -17,7 +17,7 @@ class FakeQueue:
async def get_target_queue_size(self, target_id): async def get_target_queue_size(self, target_id):
return len(self.items) return len(self.items)
async def pop_target_post(self, target_id): async def pop_target_post(self, target_id, dispatch_order="order"):
return self.items.pop(0) if self.items else None return self.items.pop(0) if self.items else None
async def push_target_post(self, target_id, payload): async def push_target_post(self, target_id, payload):
+1 -1
View File
@@ -90,7 +90,7 @@ async def run_tests():
assert progress[-1].startswith(""), f"repeat scrape must be explained: {progress[-1]}" assert progress[-1].startswith(""), f"repeat scrape must be explained: {progress[-1]}"
assert "قبلا ذخیره شده" in progress[-1] assert "قبلا ذخیره شده" in progress[-1]
stored = await repo.get_posts_by_status("pending_review", limit=100) stored = await repo.get_posts_by_status("pending_review", limit=5000)
mine = [p for p in stored if p.source_channel_id == SOURCE_ID] mine = [p for p in stored if p.source_channel_id == SOURCE_ID]
assert len(mine) == 21, f"expected 21 stored posts, got {len(mine)}" assert len(mine) == 21, f"expected 21 stored posts, got {len(mine)}"