feat(bot): implement conversational input, forwarded message auto-detection, and button-driven channel config
This commit is contained in:
+12
-2
@@ -48,12 +48,22 @@ class Repository:
|
||||
rows = await conn.fetch("SELECT * FROM sources WHERE is_active = TRUE ORDER BY id ASC;")
|
||||
return [SourceChannel(**dict(r)) for r in rows]
|
||||
|
||||
async def get_source_by_channel_id(self, channel_id: int) -> Optional[SourceChannel]:
|
||||
async def get_source_by_id(self, source_id: int) -> Optional[SourceChannel]:
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow("SELECT * FROM sources WHERE channel_id = $1;", channel_id)
|
||||
row = await conn.fetchrow("SELECT * FROM sources WHERE id = $1;", source_id)
|
||||
return SourceChannel(**dict(row)) if row else None
|
||||
|
||||
async def delete_source(self, source_id: int) -> None:
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("UPDATE sources SET is_active = FALSE WHERE id = $1;", source_id)
|
||||
|
||||
async def delete_target(self, target_id: int) -> None:
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("UPDATE targets SET is_active = FALSE WHERE id = $1;", target_id)
|
||||
|
||||
# --- Target Channels ---
|
||||
async def add_target(
|
||||
self,
|
||||
|
||||
+354
-257
@@ -1,8 +1,10 @@
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional, List, Dict
|
||||
from typing import Optional, List, Dict, Any, Tuple
|
||||
from telethon import TelegramClient, events, Button
|
||||
from telethon.tl.types import PeerChannel, PeerChat, PeerUser, Channel, Chat
|
||||
from db.models import Post, TargetChannel, SourceChannel
|
||||
from db.repository import Repository
|
||||
from core.queue import RedisQueue
|
||||
@@ -18,10 +20,12 @@ def get_persian_main_menu():
|
||||
[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("🎭 تنظیم شخصیت کانالها", resize=True), Button.text("⏰ زمانبندی و خواب کانالها", resize=True)],
|
||||
[Button.text("❓ راهنمای سیستم", resize=True)]
|
||||
]
|
||||
|
||||
def get_cancel_button():
|
||||
return [[Button.inline("❌ انصراف و بازگشت به منو", data="cancel_state")]]
|
||||
|
||||
class AdminBotService:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -49,6 +53,8 @@ class AdminBotService:
|
||||
self.client = TelegramClient(self.session_name, self.api_id, self.api_hash, proxy=get_telegram_proxy())
|
||||
self.collector = None
|
||||
self.preview_cache: Dict[str, str] = {}
|
||||
# User step-by-step state: {user_id: {"action": "...", "target_id": ...}}
|
||||
self.user_states: Dict[int, Dict[str, Any]] = {}
|
||||
|
||||
def set_collector(self, collector):
|
||||
self.collector = collector
|
||||
@@ -82,6 +88,98 @@ class AdminBotService:
|
||||
logger.info("Admin Bot connected successfully.")
|
||||
self._register_handlers()
|
||||
|
||||
# --- Helper to resolve channel from forwarded message or text ---
|
||||
async def _resolve_channel(self, event: events.NewMessage.Event) -> Tuple[Optional[int], Optional[str], Optional[str], str]:
|
||||
"""Extract (channel_id, title, username, error_message) using Userbot or Bot Client."""
|
||||
client_to_use = self.collector.client if (self.collector and self.collector.client.is_connected()) else self.client
|
||||
|
||||
# 1. Forwarded Message
|
||||
if event.message.fwd_from:
|
||||
fwd = event.message.fwd_from
|
||||
if fwd.from_id:
|
||||
try:
|
||||
entity = await client_to_use.get_entity(fwd.from_id)
|
||||
raw_id = entity.id
|
||||
ch_id = int(f"-100{raw_id}") if not str(raw_id).startswith("-100") else raw_id
|
||||
title = getattr(entity, "title", getattr(entity, "first_name", "کانال"))
|
||||
username = getattr(entity, "username", None)
|
||||
return ch_id, title, username, ""
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to resolve fwd entity: {e}")
|
||||
|
||||
if fwd.from_name:
|
||||
return None, None, None, f"⚠️ فوروارد از کانال به صورت ناشناس ارسال شده است. لطفا آیدی، یوزرنیم یا لینک کانال (مثل @channel یا https://t.me/channel) را ارسال کنید."
|
||||
|
||||
# 2. Text input (Username / Link / Numeric ID)
|
||||
text = (event.raw_text or "").strip()
|
||||
if not text:
|
||||
return None, None, None, "پیامی دریافت نشد."
|
||||
|
||||
clean = text
|
||||
if "t.me/" in clean:
|
||||
clean = clean.split("t.me/")[-1].replace("/", "").strip()
|
||||
if clean.startswith("@"):
|
||||
clean = clean[1:]
|
||||
|
||||
try:
|
||||
# Try numeric ID directly
|
||||
if clean.replace("-", "").isdigit():
|
||||
num_id = int(clean)
|
||||
entity = await client_to_use.get_entity(num_id)
|
||||
else:
|
||||
entity = await client_to_use.get_entity(clean)
|
||||
|
||||
raw_id = entity.id
|
||||
ch_id = int(f"-100{raw_id}") if not str(raw_id).startswith("-100") else raw_id
|
||||
title = getattr(entity, "title", getattr(entity, "first_name", clean))
|
||||
username = getattr(entity, "username", clean if not clean.replace("-", "").isdigit() else None)
|
||||
return ch_id, title, username, ""
|
||||
except Exception as e:
|
||||
return None, None, None, f"❌ امکان واکشی اطلاعات کانال وجود نداشت ({e}). لطفا مطمئن شوید ربات یا یوزربات در کانال عضو است."
|
||||
|
||||
# --- Target Management Card Builder ---
|
||||
async def _render_target_config(self, target_id: int) -> Tuple[str, List[List[Button]]]:
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
if not target:
|
||||
return "❌ کانال یافت نشد.", []
|
||||
|
||||
qsize = await self.queue.get_target_queue_size(target.id) if self.queue else 0
|
||||
sleep_st = f"🌙 فعال ({target.sleep_start_hour}:00 تا {target.sleep_end_hour}:00)" if target.is_sleep_enabled else "☀️ غیرفعال"
|
||||
|
||||
card_text = (
|
||||
f"🎯 <b>تنظیمات کانال مقصد:</b> <b>{target.title}</b>\n\n"
|
||||
f"• 🆔 <b>شناسه کانال:</b> <code>{target.channel_id}</code> (ID: <code>{target.id}</code>)\n"
|
||||
f"• 🔗 <b>یوزرنیم:</b> @{target.username or 'ندارد'}\n"
|
||||
f"• ⏱ <b>فاصله ارسال پستها:</b> هر <b>{target.post_interval_min} دقیقه</b>\n"
|
||||
f"• 🌙 <b>وضعیت ساعت خواب:</b> <b>{sleep_st}</b>\n"
|
||||
f"• 📥 <b>پستهای منتظر در صف:</b> <b>{qsize} پست</b>\n\n"
|
||||
f"🎭 <b>شخصیت و لحن نگارش:</b>\n"
|
||||
f"<i>{target.personality or 'پیشفرض (رسمی و روان)'}</i>\n\n"
|
||||
f"🏷 <b>فوتر و هشتگهای اختصاصی:</b>\n"
|
||||
f"<code>{target.custom_footer or 'ندارد'}</code>\n\n"
|
||||
"<i>👇 برای ویرایش هر بخش روی دکمه مربوطه کلیک کنید:</i>"
|
||||
)
|
||||
|
||||
buttons = [
|
||||
[
|
||||
Button.inline("🎭 تغییر لحن و استایل", data=f"st_pers:{target.id}"),
|
||||
Button.inline("🏷 تغییر فوتر و تگها", data=f"st_foot:{target.id}")
|
||||
],
|
||||
[
|
||||
Button.inline("⏱ تغییر فاصله ارسال", data=f"st_intv:{target.id}"),
|
||||
Button.inline("🌙 تنظیم ساعت خواب", data=f"st_slp:{target.id}")
|
||||
]
|
||||
]
|
||||
if target.is_sleep_enabled:
|
||||
buttons.append([Button.inline("☀️ غیرفعالسازی ساعت خواب", data=f"dis_slp:{target.id}")])
|
||||
|
||||
buttons.append([
|
||||
Button.inline("🗑 حذف این کانال مقصد", data=f"del_trg:{target.id}"),
|
||||
Button.inline("🔙 بازگشت به لیست کانالها", data="list_trg")
|
||||
])
|
||||
return card_text, buttons
|
||||
|
||||
# --- Review Post UI Helpers ---
|
||||
def _build_raw_post_keyboard(self, post: Post, targets: List[TargetChannel]):
|
||||
buttons = []
|
||||
sent_target_ids = set()
|
||||
@@ -162,6 +260,8 @@ class AdminBotService:
|
||||
await event.reply(f"⛔ دسترسی غیرمجاز. شناسه عددی شما: <code>{event.sender_id}</code>", parse_mode="html")
|
||||
return
|
||||
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
|
||||
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()
|
||||
@@ -171,7 +271,7 @@ class AdminBotService:
|
||||
"👋 <b>به پنل مدیریت سیستم هوشمند کپیکار خوش آمدید!</b>\n\n"
|
||||
f"• 🤖 <b>وضعیت ربات جمعآوریکننده:</b> {userbot_status}\n"
|
||||
f"• 📋 <b>شناسه کانال ادمینها:</b> <code>{self.review_channel_id}</code>\n\n"
|
||||
"از دکمههای زیر برای مدیریت کانالها، تنظیم شخصیت و زمانبندی استفاده کنید:"
|
||||
"برای مدیریت کانالها از دکمههای زیر استفاده کنید:"
|
||||
)
|
||||
await event.reply(welcome_text, parse_mode="html", buttons=get_persian_main_menu())
|
||||
|
||||
@@ -207,37 +307,17 @@ class AdminBotService:
|
||||
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 msg.edit(
|
||||
f"📩 <b>کد تایید ارسال شد!</b>\n\n"
|
||||
f"کد ارسال شده به شماره <code>{self.collector.phone}</code> را به این صورت ارسال کنید:\n"
|
||||
f"<code>/code 12345</code>",
|
||||
parse_mode="html"
|
||||
f"لطفا کد دریافت شده به شماره <code>{self.collector.phone}</code> را در پیام بعدی ارسال کنید:\n"
|
||||
f"<i>(یا به فرمت /code 12345)</i>",
|
||||
parse_mode="html",
|
||||
buttons=get_cancel_button()
|
||||
)
|
||||
except Exception as e:
|
||||
await msg.edit(f"❌ خطا در درخواست کد: {e}")
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"^/code\s+(\S+)"))
|
||||
async def cmd_code(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
if not self.collector:
|
||||
return
|
||||
code = event.pattern_match.group(1).strip()
|
||||
msg = await event.reply("⏳ در حال بررسی کد تایید...")
|
||||
result = await self.collector.submit_code(code)
|
||||
await msg.edit(result, parse_mode="html", buttons=get_persian_main_menu())
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"^/password\s+(.+)"))
|
||||
async def cmd_password(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
if not self.collector:
|
||||
return
|
||||
pwd = event.pattern_match.group(1).strip()
|
||||
msg = await event.reply("⏳ در حال تایید رمز دو مرحلهای...")
|
||||
result = await self.collector.submit_password(pwd)
|
||||
await msg.edit(result, parse_mode="html", buttons=get_persian_main_menu())
|
||||
|
||||
# --- Sources Management ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/sources|📡 کانالهای مبدا)$"))
|
||||
async def cmd_sources(event: events.NewMessage.Event):
|
||||
@@ -245,57 +325,38 @@ class AdminBotService:
|
||||
return
|
||||
sources = await self.repo.get_active_sources()
|
||||
if not sources:
|
||||
await event.reply("هیچ کانال مبدایی ثبت نشده است. از دکمه <b>➕ افزودن کانال مبدا</b> استفاده کنید.", parse_mode="html")
|
||||
await event.reply("هیچ کانال مبدایی ثبت نشده است. روی <b>➕ افزودن کانال مبدا</b> بزنید.", parse_mode="html")
|
||||
return
|
||||
|
||||
await event.reply(f"📡 <b>کانالهای مبدا فعال ({len(sources)} کانال):</b>", parse_mode="html")
|
||||
for s in sources:
|
||||
card = (
|
||||
f"📢 <b>{s.title or 'کانال'}</b>\n"
|
||||
f"📢 <b>{s.title or 'کانال مبدا'}</b>\n"
|
||||
f"• شناسه: <code>{s.channel_id}</code>\n"
|
||||
f"• یوزرنیم: @{s.username or 'ندارد'}"
|
||||
)
|
||||
buttons = [
|
||||
[
|
||||
Button.inline("📥 استخراج ۲۰ پست گذشته", data=f"hist:{s.channel_id}:20"),
|
||||
Button.inline("📥 استخراج ۵۰ پست گذشته", data=f"hist:{s.channel_id}:50"),
|
||||
Button.inline("📥 استخراج ۲۰ پست", data=f"hist:{s.channel_id}:20"),
|
||||
Button.inline("📥 استخراج ۵۰ پست", data=f"hist:{s.channel_id}:50"),
|
||||
],
|
||||
[
|
||||
Button.inline("🗑 حذف این کانال مبدا", data=f"del_src:{s.id}")
|
||||
]
|
||||
]
|
||||
await event.reply(card, parse_mode="html", buttons=buttons)
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(➕ افزودن کانال مبدا)$"))
|
||||
async def cmd_add_source_guide(event: events.NewMessage.Event):
|
||||
async def cmd_add_source_prompt(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
self.user_states[event.sender_id] = {"action": "wait_source_fwd"}
|
||||
guide = (
|
||||
"➕ <b>راهنمای افزودن کانال مبدا:</b>\n\n"
|
||||
"دستور را با فرمت زیر ارسال کنید:\n"
|
||||
"<code>/add_source <شناسه_عددی_کانال> <عنوان> [یوزرنیم]</code>\n\n"
|
||||
"<i>مثال:</i>\n"
|
||||
"<code>/add_source -1001234567890 اخبار_فوری fouri_news</code>"
|
||||
)
|
||||
await event.reply(guide, parse_mode="html")
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"^/add_source\s+(-?\d+)\s+([^\s]+)(?:\s+([^\s]+))?"))
|
||||
async def cmd_add_source(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
ch_id = int(event.pattern_match.group(1))
|
||||
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)
|
||||
|
||||
buttons = [
|
||||
[
|
||||
Button.inline("📥 استخراج ۲۰ پست گذشته این کانال", data=f"hist:{ch_id}:20"),
|
||||
Button.inline("📥 استخراج ۵۰ پست گذشته این کانال", data=f"hist:{ch_id}:50")
|
||||
]
|
||||
]
|
||||
await event.reply(
|
||||
f"✅ کانال مبدا <b>{title}</b> (<code>{ch_id}</code>) با موفقیت افزوده شد.\nآیا میخواهید پستهای قبلی این کانال را هم دریافت کنید؟",
|
||||
parse_mode="html",
|
||||
buttons=buttons
|
||||
"📡 <b>افزودن کانال مبدا جدید:</b>\n\n"
|
||||
"سادهترین روش: <b>یک پیام از کانال مورد نظر به این ربات فوروارد (Forward) کنید!</b>\n\n"
|
||||
"<i>(یا میتوانید آیدی، یوزرنیم یا لینک کانال مثل @channel_username را بفرستید)</i>"
|
||||
)
|
||||
await event.reply(guide, parse_mode="html", buttons=get_cancel_button())
|
||||
|
||||
# --- Targets Management ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/targets|🎯 کانالهای مقصد)$"))
|
||||
@@ -304,196 +365,25 @@ class AdminBotService:
|
||||
return
|
||||
targets = await self.repo.get_active_targets()
|
||||
if not targets:
|
||||
await event.reply("هیچ کانال مقصدی ثبت نشده است. از دکمه <b>➕ افزودن کانال مقصد</b> استفاده کنید.", parse_mode="html")
|
||||
await event.reply("هیچ کانال مقصدی ثبت نشده است. روی <b>➕ افزودن کانال مقصد</b> بزنید.", parse_mode="html")
|
||||
return
|
||||
lines = ["<b>🎯 کانالهای مقصد برای انتشار:</b>\n"]
|
||||
|
||||
await event.reply(f"🎯 <b>کانالهای مقصد برای انتشار ({len(targets)} کانال):</b>\nبرای تنظیمات هر کانال، روی دکمه آن بزنید:", parse_mode="html")
|
||||
for t in targets:
|
||||
qsize = await self.queue.get_target_queue_size(t.id) if self.queue else 0
|
||||
sleep_info = f"{t.sleep_start_hour}:00 تا {t.sleep_end_hour}:00" if t.is_sleep_enabled else "غیرفعال"
|
||||
lines.append(
|
||||
f"• <b>{t.title}</b> (شناسه: <code>{t.channel_id}</code> | ID دیتابیس: <code>{t.id}</code>)\n"
|
||||
f" ⏱ <b>فاصله ارسال:</b> هر <b>{t.post_interval_min}</b> دقیقه\n"
|
||||
f" 🌙 <b>ساعت خواب:</b> <b>{sleep_info}</b>\n"
|
||||
f" 📥 <b>تعداد در صف ارسال:</b> <b>{qsize}</b> پست\n"
|
||||
f" 🎭 <b>شخصیت:</b> <i>{t.personality or 'پیشفرض'}</i>\n"
|
||||
f" 🏷 <b>فوتر:</b> <code>{t.custom_footer or 'ندارد'}</code>\n"
|
||||
)
|
||||
await event.reply("\n".join(lines), parse_mode="html", buttons=get_persian_main_menu())
|
||||
card, buttons = await self._render_target_config(t.id)
|
||||
await event.reply(card, parse_mode="html", buttons=buttons)
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(➕ افزودن کانال مقصد)$"))
|
||||
async def cmd_add_target_guide(event: events.NewMessage.Event):
|
||||
async def cmd_add_target_prompt(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
self.user_states[event.sender_id] = {"action": "wait_target_fwd"}
|
||||
guide = (
|
||||
"🎯 <b>راهنمای افزودن کانال مقصد:</b>\n\n"
|
||||
"دستور را با فرمت زیر ارسال کنید:\n"
|
||||
"<code>/add_target <شناسه_کانال> <عنوان> [یوزرنیم]</code>\n\n"
|
||||
"<i>مثال:</i>\n"
|
||||
"<code>/add_target -1009876543210 دنیای_هوش_مصنوعی ai_world_chan</code>"
|
||||
)
|
||||
await event.reply(guide, parse_mode="html")
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"^/add_target\s+(-?\d+)\s+([^\s]+)(?:\s+([^\s]+))?"))
|
||||
async def cmd_add_target(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
ch_id = int(event.pattern_match.group(1))
|
||||
title = event.pattern_match.group(2)
|
||||
username = event.pattern_match.group(3)
|
||||
tid = await self.repo.add_target(channel_id=ch_id, title=title, username=username)
|
||||
await event.reply(
|
||||
f"✅ کانال مقصد <b>{title}</b> افزوده شد (ID دیتابیس: <code>{tid}</code>).\n\n"
|
||||
f"اکنون میتوانید با دکمه <b>🎭 تنظیم شخصیت کانالها</b> لحن و با <b>⏰ زمانبندی و خواب</b> فواصل ارسال را تنظیم کنید.",
|
||||
parse_mode="html",
|
||||
buttons=get_persian_main_menu()
|
||||
)
|
||||
|
||||
# --- Schedule & Sleep Management ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/schedule|⏰ زمانبندی و خواب کانالها)$"))
|
||||
async def cmd_schedule(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
targets = await self.repo.get_active_targets()
|
||||
if not targets:
|
||||
await event.reply("ابتدا یک کانال مقصد اضافه کنید.", parse_mode="html")
|
||||
return
|
||||
|
||||
text = (
|
||||
"⏰ <b>مدیریت فاصله ارسال و ساعت خواب کانالها:</b>\n\n"
|
||||
"1. <b>تنظیم فاصله ارسال پستها (به دقیقه):</b>\n"
|
||||
"<code>/set_interval <شناسه_دیتابیس_کانال> <دقیقه></code>\n"
|
||||
"<i>مثال (ارسال هر ۲۰ دقیقه یک پست):</i>\n"
|
||||
"<code>/set_interval 1 20</code>\n\n"
|
||||
"2. <b>تنظیم ساعت خواب (عدم ارسال پیام در این ساعات):</b>\n"
|
||||
"<code>/set_sleep <شناسه_دیتابیس_کانال> <ساعت_شروع> <ساعت_پایان></code>\n"
|
||||
"<i>مثال (خواب از ساعت ۲۳ شب تا ۸ صبح):</i>\n"
|
||||
"<code>/set_sleep 1 23 8</code>\n\n"
|
||||
"3. <b>غیرفعال کردن ساعت خواب:</b>\n"
|
||||
"<code>/disable_sleep <شناسه_دیتابیس_کانال></code>\n\n"
|
||||
"<b>وضعیت فعلی کانالها:</b>\n"
|
||||
)
|
||||
for t in targets:
|
||||
qsize = await self.queue.get_target_queue_size(t.id) if self.queue else 0
|
||||
sleep_st = f"🌙 خواب از {t.sleep_start_hour}:00 تا {t.sleep_end_hour}:00" if t.is_sleep_enabled else "☀️ بدون ساعت خواب"
|
||||
text += f"• ID: <code>{t.id}</code> | <b>{t.title}</b> ➔ هر <b>{t.post_interval_min}</b> دقیقه | {sleep_st} (صف: {qsize} پست)\n"
|
||||
|
||||
await event.reply(text, parse_mode="html", buttons=get_persian_main_menu())
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"^/set_interval\s+(\d+)\s+(\d+)"))
|
||||
async def cmd_set_interval(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
target_id = int(event.pattern_match.group(1))
|
||||
interval_min = int(event.pattern_match.group(2))
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
if not target:
|
||||
await event.reply(f"❌ کانال مقصد با شناسه {target_id} یافت نشد.")
|
||||
return
|
||||
await self.repo.update_target_schedule(target_id=target_id, post_interval_min=interval_min)
|
||||
await event.reply(
|
||||
f"✅ فاصله ارسال برای کانال <b>{target.title}</b> به هر <b>{interval_min} دقیقه</b> تنظیم شد.",
|
||||
parse_mode="html",
|
||||
buttons=get_persian_main_menu()
|
||||
)
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"^/set_sleep\s+(\d+)\s+(\d+)\s+(\d+)"))
|
||||
async def cmd_set_sleep(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
target_id = int(event.pattern_match.group(1))
|
||||
start_h = int(event.pattern_match.group(2))
|
||||
end_h = int(event.pattern_match.group(3))
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
if not target:
|
||||
await event.reply(f"❌ کانال مقصد با شناسه {target_id} یافت نشد.")
|
||||
return
|
||||
await self.repo.update_target_schedule(
|
||||
target_id=target_id,
|
||||
sleep_start_hour=start_h,
|
||||
sleep_end_hour=end_h,
|
||||
is_sleep_enabled=True
|
||||
)
|
||||
await event.reply(
|
||||
f"🌙 ساعت خواب برای کانال <b>{target.title}</b> از ساعت <b>{start_h}:00 تا {end_h}:00</b> فعال شد.\n(در این بازه هیچ پیامی ارسال نخواهد شد و در صف منتظر میماند).",
|
||||
parse_mode="html",
|
||||
buttons=get_persian_main_menu()
|
||||
)
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"^/disable_sleep\s+(\d+)"))
|
||||
async def cmd_disable_sleep(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
target_id = int(event.pattern_match.group(1))
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
if not target:
|
||||
await event.reply(f"❌ کانال مقصد با شناسه {target_id} یافت نشد.")
|
||||
return
|
||||
await self.repo.update_target_schedule(target_id=target_id, is_sleep_enabled=False)
|
||||
await event.reply(f"☀️ ساعت خواب برای کانال <b>{target.title}</b> غیرفعال شد.", parse_mode="html", buttons=get_persian_main_menu())
|
||||
|
||||
# --- Channel Personality & Tags Configuration ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/personality|🎭 تنظیم شخصیت کانالها)$"))
|
||||
async def cmd_personality(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
targets = await self.repo.get_active_targets()
|
||||
if not targets:
|
||||
await event.reply("ابتدا با استفاده از <b>➕ افزودن کانال مقصد</b> یک کانال مقصد اضافه کنید.", parse_mode="html")
|
||||
return
|
||||
|
||||
text = (
|
||||
"🎭 <b>تنظیم شخصیت، لحن و تگهای کانالهای مقصد:</b>\n\n"
|
||||
"برای تغییر لحن و استایل نگارش کانال از دستور زیر استفاده کنید:\n"
|
||||
"<code>/set_personality <شناسه_دیتابیس_کانال> <توضیحات لحن></code>\n\n"
|
||||
"<i>مثال:</i>\n"
|
||||
"<code>/set_personality 1 لحن جذاب و ژورنالیستی، استفاده از تیترهای بولد و ایموجیهای مرتبط</code>\n\n"
|
||||
"برای تنظیم فوتر و هشتگهای اختصاصی انتهای پست:\n"
|
||||
"<code>/set_footer <شناسه_دیتابیس_کانال> <تگها یا آیدی کانال></code>\n\n"
|
||||
"<i>مثال:</i>\n"
|
||||
"<code>/set_footer 1 🆔 @my_tech_chan\n#تکنولوژی #هوش_مصنوعی</code>\n\n"
|
||||
"<b>کانالهای موجود و شخصیت فعلی:</b>\n"
|
||||
)
|
||||
for t in targets:
|
||||
text += (
|
||||
f"• ID: <code>{t.id}</code> | <b>{t.title}</b>\n"
|
||||
f" 🎭 لحن: <i>{t.personality or 'پیشفرض'}</i>\n"
|
||||
f" 🏷 فوتر: <code>{t.custom_footer or 'ندارد'}</code>\n\n"
|
||||
)
|
||||
await event.reply(text, parse_mode="html", buttons=get_persian_main_menu())
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"^/set_personality\s+(\d+)\s+(.+)"))
|
||||
async def cmd_set_personality(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
target_id = int(event.pattern_match.group(1))
|
||||
personality = event.pattern_match.group(2).strip()
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
if not target:
|
||||
await event.reply(f"❌ کانال مقصد با شناسه {target_id} یافت نشد.")
|
||||
return
|
||||
await self.repo.update_target_personality(target_id, personality)
|
||||
await event.reply(
|
||||
f"✅ شخصیت و لحن کانال <b>{target.title}</b> با موفقیت به روز شد:\n\n<i>{personality}</i>",
|
||||
parse_mode="html",
|
||||
buttons=get_persian_main_menu()
|
||||
)
|
||||
|
||||
@self.client.on(events.NewMessage(pattern=r"^/set_footer\s+(\d+)\s+([\s\S]+)"))
|
||||
async def cmd_set_footer(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
target_id = int(event.pattern_match.group(1))
|
||||
footer = event.pattern_match.group(2).strip()
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
if not target:
|
||||
await event.reply(f"❌ کانال مقصد با شناسه {target_id} یافت نشد.")
|
||||
return
|
||||
await self.repo.update_target_footer(target_id, footer)
|
||||
await event.reply(
|
||||
f"✅ فوتر اختصاصی کانال <b>{target.title}</b> به روز شد:\n\n<code>{footer}</code>",
|
||||
parse_mode="html",
|
||||
buttons=get_persian_main_menu()
|
||||
"🎯 <b>افزودن کانال مقصد جدید:</b>\n\n"
|
||||
"سادهترین روش: <b>یک پیام از کانال مورد نظر به این ربات فوروارد (Forward) کنید!</b>\n\n"
|
||||
"<i>(یا میتوانید آیدی، یوزرنیم یا لینک کانال مثل @channel_username را بفرستید)</i>"
|
||||
)
|
||||
await event.reply(guide, parse_mode="html", buttons=get_cancel_button())
|
||||
|
||||
# --- Help ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/help|❓ راهنمای سیستم)$"))
|
||||
@@ -501,13 +391,141 @@ class AdminBotService:
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
help_text = (
|
||||
"📖 <b>راهنمای فرآیند کاری سیستم کپیکار:</b>\n\n"
|
||||
"1. 📥 <b>دریافت آنی:</b> پستهای مبدا فوری و بدون تاخیر در کانال ادمین قرار میگیرند.\n"
|
||||
"2. 🎯 <b>بازنویسی بر اساس مقصد:</b> با زدن دکمه کانال مقصد، هوش مصنوعی پست را متناسب با شخصیت و فوتر آن کانال بازنویسی میکند.\n"
|
||||
"3. ⏳ <b>صف ارسال زمانبندی شده:</b> پس از تایید، پست در صف ردیس کانال مقصد قرار میگیرد و با رعایت فاصله زمانی (Interval) و ساعات خواب (Sleep) به ترتیب منتشر میشود."
|
||||
"📖 <b>راهنمای سیستم کپیکار:</b>\n\n"
|
||||
"• <b>افزودن آسان کانالها:</b> فقط کافیست یک پیام از کانال را به ربات فوروارد کنید تا شناسه و نام آن خودکار ثبت شود!\n"
|
||||
"• <b>تنظیمات با دکمه:</b> در بخش کانالهای مقصد، روی هر کانال دکمههای تغییر لحن، تگها، فاصله زمانی و ساعت خواب وجود دارد.\n"
|
||||
"• <b>بازنویسی هوشمند:</b> پستهای مبدا به کانال ادمین میآیند و با لمس دکمه هر مقصد، متن با هوش مصنوعی و لحن همان کانال بازنویسی میشود."
|
||||
)
|
||||
await event.reply(help_text, parse_mode="html", buttons=get_persian_main_menu())
|
||||
|
||||
# --- Step-by-Step Parameter & Text Input Message Handler ---
|
||||
@self.client.on(events.NewMessage)
|
||||
async def handle_user_input(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
return
|
||||
|
||||
text = (event.raw_text or "").strip()
|
||||
|
||||
# Ignore main menu commands
|
||||
if text in ["/start", "/menu", "منو", "/stats", "📊 آمار و وضعیت ناوگان",
|
||||
"/sources", "📡 کانالهای مبدا", "/targets", "🎯 کانالهای مقصد",
|
||||
"➕ افزودن کانال مبدا", "➕ افزودن کانال مقصد", "❓ راهنمای سیستم",
|
||||
"/request_code", "🔑 درخواست کد لاگین"]:
|
||||
return
|
||||
|
||||
# Cancel command
|
||||
if text in ["/cancel", "انصراف", "لغو"]:
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
await event.reply("❌ عملیات لغو شد.", buttons=get_persian_main_menu())
|
||||
return
|
||||
|
||||
state = self.user_states.get(event.sender_id)
|
||||
if not state:
|
||||
# Direct /code or /password handling
|
||||
if text.startswith("/code "):
|
||||
code = text.split("/code ", 1)[1].strip()
|
||||
res = await self.collector.submit_code(code)
|
||||
await event.reply(res, parse_mode="html", buttons=get_persian_main_menu())
|
||||
elif text.startswith("/password "):
|
||||
pwd = text.split("/password ", 1)[1].strip()
|
||||
res = await self.collector.submit_password(pwd)
|
||||
await event.reply(res, parse_mode="html", buttons=get_persian_main_menu())
|
||||
return
|
||||
|
||||
action = state.get("action")
|
||||
|
||||
# 1. Waiting for Source Channel Forward or Text
|
||||
if action == "wait_source_fwd":
|
||||
ch_id, title, username, err = await self._resolve_channel(event)
|
||||
if err or not ch_id:
|
||||
await event.reply(err or "خطا در دریافت اطلاعات کانال. لطفا مجدد ارسال کنید:", buttons=get_cancel_button())
|
||||
return
|
||||
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
await self.repo.add_source(channel_id=ch_id, title=title, username=username)
|
||||
|
||||
buttons = [
|
||||
[
|
||||
Button.inline("📥 استخراج ۲۰ پست گذشته", data=f"hist:{ch_id}:20"),
|
||||
Button.inline("📥 استخراج ۵۰ پست گذشته", data=f"hist:{ch_id}:50")
|
||||
]
|
||||
]
|
||||
await event.reply(
|
||||
f"✅ کانال مبدا <b>{title}</b> با شناسه <code>{ch_id}</code> با موفقیت افزوده شد!\n\n"
|
||||
f"آیا مایلید پستهای قبلی این کانال هم دریافت شود؟",
|
||||
parse_mode="html",
|
||||
buttons=buttons
|
||||
)
|
||||
|
||||
# 2. Waiting for Target Channel Forward or Text
|
||||
elif action == "wait_target_fwd":
|
||||
ch_id, title, username, err = await self._resolve_channel(event)
|
||||
if err or not ch_id:
|
||||
await event.reply(err or "خطا در دریافت اطلاعات کانال. لطفا مجدد ارسال کنید:", buttons=get_cancel_button())
|
||||
return
|
||||
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
target_id = await self.repo.add_target(channel_id=ch_id, title=title, username=username)
|
||||
|
||||
card, buttons = await self._render_target_config(target_id)
|
||||
await event.reply(
|
||||
f"✅ کانال مقصد <b>{title}</b> افزوده شد!\n\nاکنون میتوانید با دکمههای زیر لحن و زمانبندی آن را تنظیم کنید:",
|
||||
parse_mode="html"
|
||||
)
|
||||
await event.reply(card, parse_mode="html", buttons=buttons)
|
||||
|
||||
# 3. Waiting for Target Personality
|
||||
elif action == "wait_personality":
|
||||
target_id = state.get("target_id")
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
await self.repo.update_target_personality(target_id, text)
|
||||
card, buttons = await self._render_target_config(target_id)
|
||||
await event.reply("✅ <b>لحن و شخصیت کانال با موفقیت به روز شد!</b>", parse_mode="html")
|
||||
await event.reply(card, parse_mode="html", buttons=buttons)
|
||||
|
||||
# 4. Waiting for Target Custom Footer
|
||||
elif action == "wait_footer":
|
||||
target_id = state.get("target_id")
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
await self.repo.update_target_footer(target_id, text)
|
||||
card, buttons = await self._render_target_config(target_id)
|
||||
await event.reply("✅ <b>فوتر و تگهای اختصاصی کانال با موفقیت به روز شد!</b>", parse_mode="html")
|
||||
await event.reply(card, parse_mode="html", buttons=buttons)
|
||||
|
||||
# 5. Waiting for Target Post Interval
|
||||
elif action == "wait_interval":
|
||||
target_id = state.get("target_id")
|
||||
if not text.isdigit() or int(text) < 1:
|
||||
await event.reply("⚠️ لطفا یک عدد معتبر (به دقیقه) ارسال کنید:", buttons=get_cancel_button())
|
||||
return
|
||||
interval_min = int(text)
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
await self.repo.update_target_schedule(target_id=target_id, post_interval_min=interval_min)
|
||||
card, buttons = await self._render_target_config(target_id)
|
||||
await event.reply(f"✅ <b>فاصله ارسال به هر {interval_min} دقیقه تغییر یافت!</b>", parse_mode="html")
|
||||
await event.reply(card, parse_mode="html", buttons=buttons)
|
||||
|
||||
# 6. Waiting for Sleep Window Hours
|
||||
elif action == "wait_sleep":
|
||||
target_id = state.get("target_id")
|
||||
parts = re.findall(r"\d+", text)
|
||||
if len(parts) < 2:
|
||||
await event.reply("⚠️ لطفا ساعت شروع و پایان را به این شکل بفرستید: <code>23 8</code> (برای ۲۳:۰۰ تا ۰۸:۰۰)", parse_mode="html", buttons=get_cancel_button())
|
||||
return
|
||||
start_h = int(parts[0]) % 24
|
||||
end_h = int(parts[1]) % 24
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
await self.repo.update_target_schedule(target_id=target_id, sleep_start_hour=start_h, sleep_end_hour=end_h, is_sleep_enabled=True)
|
||||
card, buttons = await self._render_target_config(target_id)
|
||||
await event.reply(f"🌙 <b>ساعت خواب از {start_h}:00 تا {end_h}:00 فعال شد!</b>", parse_mode="html")
|
||||
await event.reply(card, parse_mode="html", buttons=buttons)
|
||||
|
||||
# 7. Waiting for Login Code
|
||||
elif action == "wait_login_code":
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
res = await self.collector.submit_code(text)
|
||||
await event.reply(res, parse_mode="html", buttons=get_persian_main_menu())
|
||||
|
||||
# --- Inline Callback Queries ---
|
||||
@self.client.on(events.CallbackQuery)
|
||||
async def on_callback(event: events.CallbackQuery.Event):
|
||||
@@ -517,8 +535,96 @@ class AdminBotService:
|
||||
|
||||
data = event.data.decode("utf-8")
|
||||
|
||||
# 1. Historical Scraping Callback
|
||||
if data.startswith("hist:"):
|
||||
# 0. Cancel Active State
|
||||
if data == "cancel_state":
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
await event.edit("❌ عملیات لغو شد.")
|
||||
await event.answer("لغو شد.")
|
||||
|
||||
# --- Target Channel Config Buttons ---
|
||||
elif data.startswith("st_pers:"):
|
||||
target_id = int(data.split(":")[1])
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
self.user_states[event.sender_id] = {"action": "wait_personality", "target_id": target_id}
|
||||
await event.reply(
|
||||
f"🎭 <b>تغییر لحن و شخصیت کانال «{target.title}»:</b>\n\n"
|
||||
"لطفا در پیام بعدی، لحن و استایل نگارش مورد نظرتان را بفرستید:\n"
|
||||
"<i>(مثال: لحن دوستانه و پرانرژی همراه با ایموجی و تیترهای جذاب)</i>",
|
||||
parse_mode="html",
|
||||
buttons=get_cancel_button()
|
||||
)
|
||||
await event.answer()
|
||||
|
||||
elif data.startswith("st_foot:"):
|
||||
target_id = int(data.split(":")[1])
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
self.user_states[event.sender_id] = {"action": "wait_footer", "target_id": target_id}
|
||||
await event.reply(
|
||||
f"🏷 <b>تغییر فوتر و تگهای اختصاصی «{target.title}»:</b>\n\n"
|
||||
"لطفا متن فوتر، آیدی یا هشتگهایی که میخواهید انتهای هر پست قرار بگیرد را بفرستید:\n"
|
||||
"<i>(مثال: 🆔 @my_chan\n#فناوری #اخبار)</i>",
|
||||
parse_mode="html",
|
||||
buttons=get_cancel_button()
|
||||
)
|
||||
await event.answer()
|
||||
|
||||
elif data.startswith("st_intv:"):
|
||||
target_id = int(data.split(":")[1])
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
self.user_states[event.sender_id] = {"action": "wait_interval", "target_id": target_id}
|
||||
await event.reply(
|
||||
f"⏱ <b>تغییر فاصله ارسال کانال «{target.title}»:</b>\n\n"
|
||||
"فاصله زمانی بین ارسال هر پست را به <b>دقیقه</b> بفرستید:\n"
|
||||
"<i>(مثال: 30 برای ارسال هر نیم ساعت یک پست)</i>",
|
||||
parse_mode="html",
|
||||
buttons=get_cancel_button()
|
||||
)
|
||||
await event.answer()
|
||||
|
||||
elif data.startswith("st_slp:"):
|
||||
target_id = int(data.split(":")[1])
|
||||
target = await self.repo.get_target_by_id(target_id)
|
||||
self.user_states[event.sender_id] = {"action": "wait_sleep", "target_id": target_id}
|
||||
await event.reply(
|
||||
f"🌙 <b>تنظیم ساعت خواب کانال «{target.title}»:</b>\n\n"
|
||||
"ساعت شروع و پایان خواب را با یک فاصله بفرستید:\n"
|
||||
"<i>(مثال: <code>23 8</code> برای عدم ارسال پست از ساعت ۲۳:۰۰ تا ۰۸:۰۰ صبح)</i>",
|
||||
parse_mode="html",
|
||||
buttons=get_cancel_button()
|
||||
)
|
||||
await event.answer()
|
||||
|
||||
elif data.startswith("dis_slp:"):
|
||||
target_id = int(data.split(":")[1])
|
||||
await self.repo.update_target_schedule(target_id=target_id, is_sleep_enabled=False)
|
||||
card, buttons = await self._render_target_config(target_id)
|
||||
await event.edit(card, parse_mode="html", buttons=buttons)
|
||||
await event.answer("☀️ ساعت خواب غیرفعال شد.")
|
||||
|
||||
elif data.startswith("del_trg:"):
|
||||
target_id = int(data.split(":")[1])
|
||||
await self.repo.delete_target(target_id)
|
||||
await event.edit("🗑 <b>کانال مقصد با موفقیت حذف شد.</b>", parse_mode="html", buttons=None)
|
||||
await event.answer("کانال مقصد حذف شد.")
|
||||
|
||||
elif data.startswith("del_src:"):
|
||||
source_id = int(data.split(":")[1])
|
||||
await self.repo.delete_source(source_id)
|
||||
await event.edit("🗑 <b>کانال مبدا با موفقیت حذف شد.</b>", parse_mode="html", buttons=None)
|
||||
await event.answer("کانال مبدا حذف شد.")
|
||||
|
||||
elif data == "list_trg":
|
||||
targets = await self.repo.get_active_targets()
|
||||
if not targets:
|
||||
await event.edit("هیچ کانال مقصدی ثبت نشده است.")
|
||||
return
|
||||
await event.edit("🎯 <b>لیست کانالهای مقصد به روز شد:</b>")
|
||||
for t in targets:
|
||||
card, buttons = await self._render_target_config(t.id)
|
||||
await event.reply(card, parse_mode="html", buttons=buttons)
|
||||
|
||||
# --- Review Channel Buttons ---
|
||||
elif data.startswith("hist:"):
|
||||
_, ch_id_str, limit_str = data.split(":")
|
||||
ch_id = int(ch_id_str)
|
||||
limit = int(limit_str)
|
||||
@@ -535,7 +641,6 @@ class AdminBotService:
|
||||
await self.collector.scrape_channel_history(channel_id=ch_id, limit=limit, progress_callback=progress_notify)
|
||||
await event.answer("فرآیند دریافت آغاز شد.")
|
||||
|
||||
# 2. Target Selected -> Trigger On-Demand AI Rewrite for that Target
|
||||
elif data.startswith("sel_trg:"):
|
||||
_, post_id_str, target_id_str = data.split(":")
|
||||
post_id = int(post_id_str)
|
||||
@@ -579,10 +684,8 @@ class AdminBotService:
|
||||
Button.inline("🔙 انصراف / بازگشت به پست اصلی", data=f"cancel:{post_id}")
|
||||
]
|
||||
]
|
||||
|
||||
await event.edit(preview_caption, parse_mode="html", buttons=preview_buttons)
|
||||
|
||||
# 3. Add to Target's Redis Queue
|
||||
elif data.startswith("pub:"):
|
||||
_, post_id_str, target_id_str = data.split(":")
|
||||
post_id = int(post_id_str)
|
||||
@@ -597,7 +700,6 @@ class AdminBotService:
|
||||
cache_key = f"{post_id}:{target_id}"
|
||||
text_to_publish = self.preview_cache.get(cache_key) or post.raw_text or ""
|
||||
|
||||
# Push to Target's Redis Queue
|
||||
payload = {
|
||||
"post_id": post.id,
|
||||
"text": text_to_publish,
|
||||
@@ -609,10 +711,8 @@ class AdminBotService:
|
||||
await self.queue.push_target_post(target.id, payload)
|
||||
ADMIN_ACTIONS_TOTAL.labels(action="approved").inc()
|
||||
|
||||
# Update database record
|
||||
await self.repo.record_post_published_to_target(post_id, target.id, target.title or "Target")
|
||||
|
||||
# Reload updated post with publication history
|
||||
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)
|
||||
@@ -625,7 +725,6 @@ class AdminBotService:
|
||||
)
|
||||
await event.answer(f"به صف {target.title} افزوده شد!")
|
||||
|
||||
# 4. Cancel Preview & Restore Original Card
|
||||
elif data.startswith("cancel:"):
|
||||
_, post_id_str = data.split(":")
|
||||
post_id = int(post_id_str)
|
||||
@@ -641,7 +740,6 @@ class AdminBotService:
|
||||
await event.edit(caption, parse_mode="html", buttons=buttons)
|
||||
await event.answer("پیشنمایش لغو شد.")
|
||||
|
||||
# 5. Reject Post
|
||||
elif data.startswith("rej:"):
|
||||
_, post_id_str = data.split(":")
|
||||
post_id = int(post_id_str)
|
||||
@@ -656,7 +754,6 @@ class AdminBotService:
|
||||
)
|
||||
await event.answer("پست بایگانی شد.")
|
||||
|
||||
# 6. Soft Delete & Remove Message from Admin Channel
|
||||
elif data.startswith("del_msg:"):
|
||||
_, post_id_str = data.split(":")
|
||||
post_id = int(post_id_str)
|
||||
|
||||
Reference in New Issue
Block a user