Team Ai
Datasetpublic

ysn-rfd/text-dataset-tiny-code-script-py-format

USED of tahamajs/medicine_ds_persian for .parquet file USED of Alijafarixcs2/persian-it-llama2-2k for .parquet file USED of Abirate/english_quotes for .jsonl file NEW FILES (05/12/2025) NEW FILES (12/26/2025) NEW FILES (02/15/2026)

sourceHugging Faceapache-2.0updated 4mo agoView on Hugging Face
3likes1.7kdownloads
main.py660 linesDownload Raw Back to 2
1# main.py2 3import os4import logging5import asyncio6import httpx7import time8from telegram import Update9from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes10from openai import AsyncOpenAI11from keep_alive import start_keep_alive12 13# وارد کردن مدیر داده‌ها و پنل ادمین14import data_manager15import admin_panel16 17# شروع سرویس نگه داشتن ربات فعال18start_keep_alive()19 20# --- بهبود لاگینگ ---21logging.basicConfig(22    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 23    level=logging.INFO,24    filename=data_manager.LOG_FILE, 25    filemode='a'26)27logger = logging.getLogger(__name__)28 29try:30    with open(data_manager.LOG_FILE, 'a') as f:31        f.write("")32except Exception as e:33    print(f"FATAL: Could not write to log file at {data_manager.LOG_FILE}. Error: {e}")34    logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")35 36# --- ایجاد یک کلاینت HTTP بهینه‌سازی‌شده ---37http_client = httpx.AsyncClient(38    http2=True,39    limits=httpx.Limits(max_keepalive_connections=20, max_connections=100, keepalive_expiry=30.0),40    timeout=httpx.Timeout(timeout=60.0, connect=10.0, read=45.0, write=10.0)41)42 43# کلاینت OpenAI (HuggingFace)44client = AsyncOpenAI(45    base_url="https://router.huggingface.co/v1",46    api_key=os.environ["HF_TOKEN"],47    http_client=http_client48)49 50# --- دیکشنری برای مدیریت وظایف پس‌زمینه هر کاربر ---51user_tasks = {}52group_tasks = {}53 54# --- توابع کمکی برای مدیریت ریپلای ---55def extract_reply_info(update: Update, context: ContextTypes.DEFAULT_TYPE) -> tuple:56    """استخراج اطلاعات از ریپلای"""57    message = update.message58    59    # اگر ریپلای وجود نداشت60    if not message or not message.reply_to_message:61        return None, None, None, None62    63    replied_message = message.reply_to_message64    65    # اطلاعات پیام ریپلای شده66    replied_user_id = replied_message.from_user.id if replied_message.from_user else None67    replied_user_name = replied_message.from_user.first_name if replied_message.from_user else "Unknown"68    69    # استخراج متن پیام ریپلای شده70    replied_text = ""71    if replied_message.text:72        replied_text = replied_message.text73    elif replied_message.caption:74        replied_text = replied_message.caption75    76    # بررسی اینکه آیا ریپلای به ربات است77    is_reply_to_bot = False78    if replied_message.from_user:79        # بررسی اینکه آیا پیام از خود ربات است80        if replied_message.from_user.is_bot:81            is_reply_to_bot = True82        # یا آیا پیام حاوی منشن ربات است83        elif context.bot and context.bot.username and replied_message.text:84            if f"@{context.bot.username}" in replied_message.text:85                is_reply_to_bot = True86    87    return replied_user_id, replied_user_name, replied_text, is_reply_to_bot88 89def format_reply_message(user_message: str, replied_user_name: str, replied_text: str, is_reply_to_bot: bool = False, current_user_name: str = None) -> str:90    """فرمت‌دهی پیام با در نظر گرفتن ریپلای"""91    if not replied_text:92        return user_message93    94    # محدود کردن طول متن ریپلای شده95    if len(replied_text) > 100:96        replied_preview = replied_text[:97] + "..."97    else:98        replied_preview = replied_text99    100    # حذف منشن ربات از متن ریپلای شده اگر وجود دارد101    replied_preview = replied_preview.replace("@", "(at)")  # جایگزین موقت برای جلوگیری از منشن102    103    if is_reply_to_bot:104        # ریپلای به ربات105        if current_user_name:106            return f"📎 {current_user_name} در پاسخ به ربات: «{replied_preview}»\n\n{user_message}"107        else:108            return f"📎 ریپلای به ربات: «{replied_preview}»\n\n{user_message}"109    else:110        # ریپلای به کاربر دیگر111        if current_user_name:112            return f"📎 {current_user_name} در پاسخ به {replied_user_name}: «{replied_preview}»\n\n{user_message}"113        else:114            return f"📎 ریپلای به {replied_user_name}: «{replied_preview}»\n\n{user_message}"115 116def create_reply_info_dict(replied_user_id, replied_user_name, replied_text, is_reply_to_bot):117    """ایجاد دیکشنری اطلاعات ریپلای"""118    if not replied_text:119        return None120    121    return {122        'replied_user_id': replied_user_id,123        'replied_user_name': replied_user_name,124        'replied_text': replied_text[:500],  # محدود کردن طول125        'is_reply_to_bot': is_reply_to_bot,126        'timestamp': time.time()127    }128 129# --- توابع کمکی برای مدیریت وظایف ---130def _cleanup_task(task: asyncio.Task, task_dict: dict, task_id: int):131    if task_id in task_dict and task_dict[task_id] == task:132        del task_dict[task_id]133        logger.info(f"Cleaned up finished task for ID {task_id}.")134    try:135        exception = task.exception()136        if exception:137            logger.error(f"Background task for ID {task_id} failed: {exception}")138    except asyncio.CancelledError:139        logger.info(f"Task for ID {task_id} was cancelled.")140    except asyncio.InvalidStateError:141        pass142 143async def _process_user_request(update: Update, context: ContextTypes.DEFAULT_TYPE, custom_message: str = None, reply_info: dict = None):144    """پردازش درخواست کاربر در چت خصوصی"""145    chat_id = update.effective_chat.id146    user_message = custom_message if custom_message is not None else (update.message.text or update.message.caption or "")147    user_id = update.effective_user.id148    149    if not user_message:150        logger.warning(f"No message content for user {user_id}")151        return152    153    # اگر reply_info از بیرون داده نشده، استخراج کن154    if reply_info is None:155        replied_user_id, replied_user_name, replied_text, is_reply_to_bot = extract_reply_info(update, context)156        reply_info = create_reply_info_dict(replied_user_id, replied_user_name, replied_text, is_reply_to_bot)157    158    # اگر ریپلای وجود داشت، متن را فرمت کن159    if reply_info and reply_info.get('replied_text'):160        formatted_message = format_reply_message(161            user_message, 162            reply_info.get('replied_user_name', 'Unknown'),163            reply_info.get('replied_text'),164            reply_info.get('is_reply_to_bot', False)165        )166    else:167        formatted_message = user_message168    169    start_time = time.time()170 171    try:172        await context.bot.send_chat_action(chat_id=chat_id, action="typing")173        174        # دریافت context کاربر175        user_context = data_manager.get_context_for_api(user_id)176        177        # اضافه کردن پیام کاربر به context (با فرمت ریپلای اگر وجود داشت)178        data_manager.add_to_user_context_with_reply(user_id, "user", formatted_message, reply_info)179        180        # ایجاد لیست پیام‌ها برای API181        messages = user_context.copy()182        messages.append({"role": "user", "content": formatted_message})183        184        logger.info(f"User {user_id} sending {len(messages)} messages to AI")185        if reply_info and reply_info.get('replied_text'):186            logger.info(f"Reply info: replied_to={reply_info.get('replied_user_name')}, is_to_bot={reply_info.get('is_reply_to_bot')}")187        188        response = await client.chat.completions.create(189            model="mlabonne/gemma-3-27b-it-abliterated:featherless-ai",190            messages=messages,191            temperature=0.7,192            top_p=0.95,193            stream=False,194        )195        196        end_time = time.time()197        response_time = end_time - start_time198        data_manager.update_response_stats(response_time)199        200        ai_response = response.choices[0].message.content201        202        # اضافه کردن پاسخ هوش مصنوعی به context203        data_manager.add_to_user_context(user_id, "assistant", ai_response)204        205        await update.message.reply_text(ai_response)206        data_manager.update_user_stats(user_id, update.effective_user)207 208    except httpx.TimeoutException:209        logger.warning(f"Request timed out for user {user_id}.")210        await update.message.reply_text("⏱️ ارتباط با سرور هوش مصنوعی طولانی شد. لطفاً دوباره تلاش کنید.")211    except Exception as e:212        logger.error(f"Error while processing message for user {user_id}: {e}")213        await update.message.reply_text("❌ متاسفانه در پردازش درخواست شما مشکلی پیش آمد. لطفاً دوباره تلاش کنید.")214 215async def _process_group_request(update: Update, context: ContextTypes.DEFAULT_TYPE, custom_message: str = None, reply_info: dict = None):216    """پردازش درخواست در گروه"""217    chat_id = update.effective_chat.id218    user_message = custom_message if custom_message is not None else (update.message.text or update.message.caption or "")219    user_id = update.effective_user.id220    user_name = update.effective_user.first_name221    222    if not user_message:223        logger.warning(f"No message content for group {chat_id}, user {user_id}")224        return225    226    # اگر reply_info از بیرون داده نشده، استخراج کن227    if reply_info is None:228        replied_user_id, replied_user_name, replied_text, is_reply_to_bot = extract_reply_info(update, context)229        reply_info = create_reply_info_dict(replied_user_id, replied_user_name, replied_text, is_reply_to_bot)230    231    # اگر ریپلای وجود داشت، متن را فرمت کن232    if reply_info and reply_info.get('replied_text'):233        formatted_message = format_reply_message(234            user_message,235            reply_info.get('replied_user_name', 'Unknown'),236            reply_info.get('replied_text'),237            reply_info.get('is_reply_to_bot', False),238            user_name239        )240    else:241        formatted_message = user_message242    243    start_time = time.time()244 245    try:246        await context.bot.send_chat_action(chat_id=chat_id, action="typing")247        248        # بررسی حالت context249        context_mode = data_manager.get_context_mode()250        251        if context_mode == 'group_shared':252            # حالت مشترک گروه: فقط از context گروه استفاده می‌شود253            messages = data_manager.get_context_for_api_group(chat_id)254            data_manager.add_to_group_context_with_reply(chat_id, "user", formatted_message, user_name, reply_info)255            256        elif context_mode == 'hybrid':257            # حالت ترکیبی: از هر دو context استفاده می‌شود258            messages = data_manager.get_hybrid_context_for_api(user_id, chat_id)259            data_manager.add_to_hybrid_context_with_reply(user_id, chat_id, "user", formatted_message, user_name, reply_info)260            261        else:  # separate (پیش‌فرض)262            # حالت جداگانه: هر کاربر context خودش را دارد263            messages = data_manager.get_context_for_api(user_id)264            data_manager.add_to_user_context_with_reply(user_id, "user", formatted_message, reply_info)265        266        # اضافه کردن پیام جدید267        if context_mode == 'group_shared':268            messages.append({"role": "user", "content": f"{user_name}: {formatted_message}"})269        else:270            messages.append({"role": "user", "content": formatted_message})271        272        logger.info(f"Group {chat_id} ({context_mode} mode) sending {len(messages)} messages to AI")273        if reply_info and reply_info.get('replied_text'):274            logger.info(f"Reply info: replied_to={reply_info.get('replied_user_name')}, is_to_bot={reply_info.get('is_reply_to_bot')}")275        276        response = await client.chat.completions.create(277            model="mlabonne/gemma-3-27b-it-abliterated:featherless-ai",278            messages=messages,279            temperature=0.7,280            top_p=0.95,281            stream=False,282        )283        284        end_time = time.time()285        response_time = end_time - start_time286        data_manager.update_response_stats(response_time)287        288        ai_response = response.choices[0].message.content289        290        # ذخیره پاسخ بر اساس حالت context291        if context_mode == 'group_shared':292            data_manager.add_to_group_context(chat_id, "assistant", ai_response)293        elif context_mode == 'hybrid':294            # برای پاسخ‌های هوش مصنوعی، reply_info را None می‌کنیم295            data_manager.add_to_hybrid_context_with_reply(user_id, chat_id, "assistant", ai_response, None, None)296        else:297            data_manager.add_to_user_context(user_id, "assistant", ai_response)298        299        # به‌روزرسانی آمار گروه با user_id300        data_manager.update_group_stats(chat_id, update.effective_chat, user_id)301        302        await update.message.reply_text(ai_response)303 304    except httpx.TimeoutException:305        logger.warning(f"Request timed out for group {chat_id}.")306        await update.message.reply_text("⏱️ ارتباط با سرور هوش مصنوعی طولانی شد. لطفاً دوباره تلاش کنید.")307    except Exception as e:308        logger.error(f"Error while processing message for group {chat_id}: {e}")309        await update.message.reply_text("❌ متاسفانه در پردازش درخواست شما مشکلی پیش آمد. لطفاً دوباره تلاش کنید.")310 311# --- هندلرهای اصلی ربات ---312async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:313    user = update.effective_user314    user_id = user.id315    chat = update.effective_chat316    317    if chat.type in ['group', 'supergroup']:318        # در گروه319        data_manager.update_group_stats(chat.id, chat, user_id)320        321        welcome_msg = data_manager.DATA.get('group_welcome_message', 322            "سلام به همه! 🤖\n\nمن یک ربات هوشمند هستم. برای استفاده از من در گروه:\n1. مستقیم با من چت کنید\n2. یا با منشن کردن من سوال بپرسید\n3. یا روی پیام‌ها ریپلای کنید")323        324        context_mode = data_manager.get_context_mode()325        mode_info = ""326        if context_mode == 'group_shared':327            mode_info = "\n\n📝 **نکته:** در این گروه از context مشترک استفاده می‌شود. همه کاربران تاریخچه مکالمه یکسانی دارند (تا 16384 توکن)."328        elif context_mode == 'hybrid':329            mode_info = "\n\n📝 **نکته:** در این گروه از context ترکیبی استفاده می‌شود."330        331        await update.message.reply_html(332            welcome_msg + mode_info,333            disable_web_page_preview=True334        )335    else:336        # در چت خصوصی337        data_manager.update_user_stats(user_id, user)338        339        welcome_msg = data_manager.DATA.get('welcome_message', 340            "سلام {user_mention}! 🤖\n\nمن یک ربات هوشمند هستم. هر سوالی دارید بپرسید.\n\n💡 **نکته:** می‌توانید روی پیام‌های من ریپلای کنید تا من ارتباط را بهتر بفهمم.")341        342        await update.message.reply_html(343            welcome_msg.format(user_mention=user.mention_html()),344            disable_web_page_preview=True345        )346 347async def clear_chat(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:348    """پاک کردن تاریخچه چت"""349    user_id = update.effective_user.id350    chat = update.effective_chat351    352    if chat.type in ['group', 'supergroup']:353        # پاک کردن context گروه354        data_manager.clear_group_context(chat.id)355        356        context_mode = data_manager.get_context_mode()357        if context_mode == 'group_shared':358            await update.message.reply_text(359                "🧹 تاریخچه مکالمه گروه پاک شد.\n"360                "از این لحظه مکالمه جدیدی برای همه اعضای گروه شروع خواهد شد."361            )362        else:363            await update.message.reply_text(364                "🧹 تاریخچه مکالمه شخصی شما در این گروه پاک شد.\n"365                "از این لحظه مکالمه جدیدی شروع خواهد شد."366            )367    else:368        # پاک کردن context کاربر369        data_manager.clear_user_context(user_id)370        371        await update.message.reply_text(372            "🧹 تاریخچه مکالمه شما پاک شد.\n"373            "از این لحظه مکالمه جدیدی شروع خواهد شد."374        )375 376async def context_info(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:377    """نمایش اطلاعات context"""378    user_id = update.effective_user.id379    chat = update.effective_chat380    381    if chat.type in ['group', 'supergroup']:382        context_mode = data_manager.get_context_mode()383        384        if context_mode == 'group_shared':385            context_summary = data_manager.get_group_context_summary(chat.id)386            await update.message.reply_text(387                f"📊 **اطلاعات تاریخچه مکالمه گروه:**\n\n"388                f"{context_summary}\n\n"389                f"**حالت:** مشترک بین همه اعضای گروه\n"390                f"**سقف توکن:** 16384 توکن\n"391                f"برای پاک کردن تاریخچه از دستور /clear استفاده کنید."392            )393        elif context_mode == 'hybrid':394            group_summary = data_manager.get_group_context_summary(chat.id)395            user_summary = data_manager.get_context_summary(user_id)396            await update.message.reply_text(397                f"📊 **اطلاعات تاریخچه مکالمه (حالت ترکیبی):**\n\n"398                f"**گروه:** {group_summary}\n"399                f"**شخصی:** {user_summary}\n\n"400                f"برای پاک کردن تاریخچه از دستور /clear استفاده کنید."401            )402        else:403            context_summary = data_manager.get_context_summary(user_id)404            await update.message.reply_text(405                f"📊 **اطلاعات تاریخچه مکالمه شخصی شما در این گروه:**\n\n"406                f"{context_summary}\n\n"407                f"برای پاک کردن تاریخچه از دستور /clear استفاده کنید."408            )409    else:410        context_summary = data_manager.get_context_summary(user_id)411        412        await update.message.reply_text(413            f"📊 **اطلاعات تاریخچه مکالمه شما:**\n\n"414            f"{context_summary}\n\n"415            f"برای پاک کردن تاریخچه از دستور /clear استفاده کنید."416        )417 418async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:419    """نمایش دستورات کمک"""420    chat = update.effective_chat421    422    if chat.type in ['group', 'supergroup']:423        help_text = (424            "🤖 **دستورات ربات در گروه:**\n\n"425            "🟢 `/start` - شروع کار با ربات در گروه\n"426            "🟢 `/clear` - پاک کردن تاریخچه مکالمه\n"427            "🟢 `/context` - نمایش اطلاعات تاریخچه مکالمه\n"428            "🟢 `/help` - نمایش این پیام راهنما\n\n"429            "📝 **نکته:** ربات به سه صورت کار می‌کند:\n"430            "1. پاسخ به پیام‌های مستقیم\n"431            "2. پاسخ وقتی منشن می‌شوید\n"432            "3. پاسخ به ریپلای‌ها (روی پیام‌های من یا دیگران)\n\n"433            f"**حالت فعلی:** {data_manager.get_context_mode()}\n"434            "• separate: هر کاربر تاریخچه جداگانه (8192 توکن)\n"435            "• group_shared: تاریخچه مشترک برای همه (16384 توکن)\n"436            "• hybrid: ترکیب هر دو\n\n"437            "💡 **توجه:** در حالت group_shared، ربات می‌تواند تا 16384 توکن از تاریخچه مکالمه گروه را به خاطر بسپارد."438        )439    else:440        help_text = (441            "🤖 **دستورات ربات:**\n\n"442            "🟢 `/start` - شروع کار با ربات\n"443            "🟢 `/clear` - پاک کردن تاریخچه مکالمه\n"444            "🟢 `/context` - نمایش اطلاعات تاریخچه مکالمه\n"445            "🟢 `/help` - نمایش این پیام راهنما\n\n"446            "📝 **نکته:** ربات تاریخچه مکالمه شما را تا ۲۰۴۸ توکن ذخیره می‌کند. "447            "می‌توانید روی پیام‌های من ریپلای کنید تا من ارتباط را بهتر بفهمم.\n"448            "برای شروع مکالمه جدید از دستور /clear استفاده کنید."449        )450    451    await update.message.reply_text(help_text, parse_mode='Markdown')452 453async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:454    # بررسی وجود پیام455    if not update.message:456        return457    458    user_id = update.effective_user.id459    chat = update.effective_chat460    461    # بررسی مسدود بودن کاربر462    if data_manager.is_user_banned(user_id):463        logger.info(f"Banned user {user_id} tried to send a message.")464        return465    466    # بررسی حالت نگهداری467    if data_manager.DATA.get('maintenance_mode', False) and user_id not in admin_panel.ADMIN_IDS:468        await update.message.reply_text("🔧 ربات در حال حاضر در حالت نگهداری قرار دارد. لطفاً بعداً تلاش کنید.")469        return470 471    # بررسی کلمات مسدود شده472    message_text = update.message.text or update.message.caption or ""473    if not message_text:474        return475    476    if data_manager.contains_blocked_words(message_text):477        logger.info(f"User {user_id} sent a message with a blocked word.")478        return479 480    if chat.type in ['group', 'supergroup']:481        # پردازش در گروه482        if chat.id in group_tasks and not group_tasks[chat.id].done():483            group_tasks[chat.id].cancel()484            logger.info(f"Cancelled previous task for group {chat.id} to start a new one.")485 486        task = asyncio.create_task(_process_group_request(update, context))487        group_tasks[chat.id] = task488        task.add_done_callback(lambda t: _cleanup_task(t, group_tasks, chat.id))489    else:490        # پردازش در چت خصوصی491        if user_id in user_tasks and not user_tasks[user_id].done():492            user_tasks[user_id].cancel()493            logger.info(f"Cancelled previous task for user {user_id} to start a new one.")494 495        task = asyncio.create_task(_process_user_request(update, context))496        user_tasks[user_id] = task497        task.add_done_callback(lambda t: _cleanup_task(t, user_tasks, user_id))498 499async def mention_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:500    """هندلر برای زمانی که ربات در گروه منشن می‌شود"""501    if not update.message:502        return503    504    if update.effective_chat.type in ['group', 'supergroup']:505        # استخراج اطلاعات ریپلای506        replied_user_id, replied_user_name, replied_text, is_reply_to_bot = extract_reply_info(update, context)507        reply_info = create_reply_info_dict(replied_user_id, replied_user_name, replied_text, is_reply_to_bot)508        509        # حذف منشن از متن510        message_text = update.message.text or ""511        bot_username = context.bot.username512        513        # حذف منشن ربات از متن514        if bot_username and f"@{bot_username}" in message_text:515            message_text = message_text.replace(f"@{bot_username}", "").strip()516        517        if message_text:518            # پردازش پیام مانند حالت عادی اما با متن فرمت‌شده519            user_name = update.effective_user.first_name520            521            # اگر ریپلای وجود داشت، متن را فرمت کن522            if reply_info and reply_info.get('replied_text'):523                formatted_message = format_reply_message(524                    message_text,525                    reply_info.get('replied_user_name', 'Unknown'),526                    reply_info.get('replied_text'),527                    reply_info.get('is_reply_to_bot', False),528                    user_name529                )530            else:531                formatted_message = message_text532            533            # پردازش درخواست534            if update.effective_chat.id in group_tasks and not group_tasks[update.effective_chat.id].done():535                group_tasks[update.effective_chat.id].cancel()536                logger.info(f"Cancelled previous task for group {update.effective_chat.id} to start a new one.")537 538            task = asyncio.create_task(_process_group_request(update, context, formatted_message, reply_info))539            group_tasks[update.effective_chat.id] = task540            task.add_done_callback(lambda t: _cleanup_task(t, group_tasks, update.effective_chat.id))541        else:542            await update.message.reply_text("بله؟ چگونه می‌توانم کمک کنم؟")543 544async def reply_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:545    """هندلر مخصوص ریپلای‌ها"""546    # بررسی وجود پیام547    if not update.message:548        return549    550    # فقط ریپلای‌هایی که متن دارند551    message_text = update.message.text or update.message.caption or ""552    if not message_text:553        return554    555    user_id = update.effective_user.id556    chat = update.effective_chat557    558    # بررسی مسدود بودن کاربر559    if data_manager.is_user_banned(user_id):560        return561    562    # بررسی حالت نگهداری563    if data_manager.DATA.get('maintenance_mode', False) and user_id not in admin_panel.ADMIN_IDS:564        return565 566    # بررسی کلمات مسدود شده567    if data_manager.contains_blocked_words(message_text):568        return569 570    if chat.type in ['group', 'supergroup']:571        # پردازش در گروه572        if chat.id in group_tasks and not group_tasks[chat.id].done():573            group_tasks[chat.id].cancel()574            logger.info(f"Cancelled previous task for group {chat.id} to start a new one.")575 576        task = asyncio.create_task(_process_group_request(update, context))577        group_tasks[chat.id] = task578        task.add_done_callback(lambda t: _cleanup_task(t, group_tasks, chat.id))579    else:580        # پردازش در چت خصوصی581        if user_id in user_tasks and not user_tasks[user_id].done():582            user_tasks[user_id].cancel()583            logger.info(f"Cancelled previous task for user {user_id} to start a new one.")584 585        task = asyncio.create_task(_process_user_request(update, context))586        user_tasks[user_id] = task587        task.add_done_callback(lambda t: _cleanup_task(t, user_tasks, user_id))588 589def main() -> None:590    token = os.environ.get("BOT_TOKEN")591    if not token:592        logger.error("BOT_TOKEN not set in environment variables!")593        return594 595    application = (596        Application.builder()597        .token(token)598        .concurrent_updates(True)599        .build()600    )601 602    # هندلرهای کاربران603    application.add_handler(CommandHandler("start", start))604    application.add_handler(CommandHandler("clear", clear_chat))605    application.add_handler(CommandHandler("context", context_info))606    application.add_handler(CommandHandler("help", help_command))607    608    # هندلر برای ریپلای‌ها609    application.add_handler(MessageHandler(610        filters.TEXT & filters.REPLY, 611        reply_handler612    ))613    614    # هندلر برای منشن در گروه615    application.add_handler(MessageHandler(616        filters.TEXT & filters.Entity("mention") & filters.ChatType.GROUPS, 617        mention_handler618    ))619    620    # هندلر برای پیام‌های متنی معمولی (بدون ریپلای و بدون منشن)621    application.add_handler(MessageHandler(622        filters.TEXT & ~filters.COMMAND & filters.ChatType.PRIVATE & ~filters.REPLY, 623        handle_message624    ))625    626    # هندلر برای پیام‌های متنی در گروه (بدون منشن و بدون ریپلای)627    application.add_handler(MessageHandler(628        filters.TEXT & ~filters.COMMAND & filters.ChatType.GROUPS & ~filters.Entity("mention") & ~filters.REPLY, 629        handle_message630    ))631    632    # راه‌اندازی و ثبت هندلرهای پنل ادمین633    admin_panel.setup_admin_handlers(application)634 635    # تنظیم هندلر خطا636    async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:637        logger.error(f"Exception while handling an update: {context.error}")638        try:639            if update and update.message:640                await update.message.reply_text("❌ خطایی در پردازش درخواست شما رخ داد.")641        except:642            pass643    644    application.add_error_handler(error_handler)645 646    port = int(os.environ.get("PORT", 8443))647    webhook_url = os.environ.get("RENDER_EXTERNAL_URL") + "/webhook"648    649    if webhook_url:650        application.run_webhook(651            listen="0.0.0.0",652            port=port,653            webhook_url=webhook_url,654            url_path="webhook"655        )656    else:657        application.run_polling()658 659if __name__ == "__main__":660    main()