# ============================================================================== # 🟢 پارت 1: وارد کردن کتابخانه‌ها و ماژول‌ها (Imports) # ============================================================================== import os import time import threading import random import aiohttp import traceback import asyncio import base64 import mimetypes import io import json import datetime import string import uuid import concurrent.futures import sqlite3 import copy import cv2 import tempfile import requests import ffmpeg from flask import Flask, request, send_from_directory from rubpy.bot import BotClient, filters from huggingface_hub import AsyncInferenceClient, HfApi, hf_hub_download from PIL import Image from pydub import AudioSegment from deep_translator import GoogleTranslator # ============================================================================== # 🟢 پارت 2: تنظیمات سرورها، مدل‌های تغییر صدا و کلون صدا # ============================================================================== # --- تنظیمات آدرس سرورهای تغییر صدا و ساخت/ویرایش تصاویر --- VC_BASE_URL = "https://sada8888-sada.hf.space" LEGACY_BASE_URL = "https://ezmarynoori-rvc.hf.space" IMAGE_SPACE_URL = "https://sada8888-tts3.hf.space" GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") GITHUB_USER = os.environ.get("GITHUB_USER", "hajiliker6-source") GITHUB_REPO = os.environ.get("GITHUB_REPO", "Action") SPACE_HOST = os.environ.get("SPACE_HOST", "") RUBIKA_SPACE_URL = f"https://{SPACE_HOST}" if SPACE_HOST else "" LEGACY_MODELS = { "1": {"name": "شادمهر", "url": "https://huggingface.co/amirmatrix/shadmehr/resolve/main/added_IVF722_Flat_nprobe_1_shadmehr_v2.zip?download=true", "gender": "male"}, "2": {"name": "معین", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Moein.zip?download=true", "gender": "male"}, "3": {"name": "بیلی آیلیش", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Billie.zip?download=true", "gender": "female"}, "4": {"name": "محسن چاوشی", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/chavoshi250.zip?download=true", "gender": "male"}, "5": {"name": "سیاوش قمیشی", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Ghomayshi250.zip?download=true", "gender": "male"}, "6": {"name": "یاس", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Yas300.zip?download=true", "gender": "male"}, "7": {"name": "عادل فردوسی‌پور", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Adel.zip?download=true", "gender": "male"}, "8": {"name": "باب اسفنجی", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Bab_Asfanj300.zip?download=true", "gender": "male"} } STANDARD_MODELS = { "9": {"name": "علی سورنا", "ref": "https://huggingface.co/datasets/Hamed744/mp3/resolve/main/%D9%85%D8%AF%D9%84%20%D8%B5%D8%AF%D8%A7%DB%8C%20%D8%B3%D9%88%D8%B1%D9%86%D8%A7.mp3?download=true"}, "10": {"name": "رونالدو", "ref": "https://huggingface.co/datasets/Hamed744/mp3/resolve/main/%D8%B1%D9%88%D9%86%D8%A7%D9%84%D8%AF%D9%88.wav?download=true"}, "11": {"name": "مسی", "ref": "https://huggingface.co/datasets/Hamed744/mp3/resolve/main/%D8%B5%D8%AF%D8%A7%DB%8C-%D9%85%D8%B3%DB%8C.mp3?download=true"}, "12": {"name": "مریم", "ref": "https://huggingface.co/datasets/Hamed744/mp3/resolve/main/%D8%B5%D8%AF%D8%A7%DB%8C%20%D8%AE%D8%A7%D9%86%D9%85.wav?download=true"} } # ============================================================================== # 🟢 پارت 3: متغیرهای ادمین، سیستم ضد اسپم و توابع تبدیل تاریخ # ============================================================================== # --- کد مدیریت --- ADMIN_CODE = "3011" BOT_GUID = None # ======================================================= # سیستم ضد اسپم کاربر-محور (بهینه شده برای جلوگیری از نشت مموری) # ======================================================= user_message_times = {} def is_user_spamming(chat_id): now = time.time() times = user_message_times.get(chat_id, []) # فقط زمان‌های ۳ ثانیه اخیر رو نگه دار times =[t for t in times if now - t < 3.0] times.append(now) user_message_times[chat_id] = times if len(times) > 4: return True return False def gregorian_to_jalali(gy, gm, gd): g_d_m =[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] gy2 = (gy + 1) if (gm > 2) else gy days = 355666 + (365 * gy) + ((gy2 + 3) // 4) - ((gy2 + 99) // 100) + ((gy2 + 399) // 400) + gd + g_d_m[gm - 1] jy = -1595 + (33 * (days // 12053)) days %= 12053 jy += 4 * (days // 1461) days %= 1461 if days > 365: jy += (days - 1) // 365 days = (days - 1) % 365 if days < 186: jm = 1 + (days // 31) jd = 1 + (days % 31) else: jm = 7 + ((days - 186) // 30) jd = 1 + ((days - 186) % 30) return jy, jm, jd # ============================================================================== # 🟢 پارت 4: دیتابیس SQLite (نسخه جراحی + بازیابی هوشمند کاربران ویژه از JSON قدیمی) # ============================================================================== import os import sqlite3 import json import copy import threading DB_FILE = "/data/users_v6.db" OLD_DB_V3 = "/data/users_v3.db" OLD_JSON_BAK = "users_db.json.bak" # نام فایل بکاپ جیسون شما last_saved_state = {} recent_messages_dict = {} db_lock = threading.Lock() msg_cache_lock = threading.Lock() def init_sqlite_db(): try: os.makedirs(os.path.dirname(DB_FILE), exist_ok=True) is_first_run = not os.path.exists(DB_FILE) conn = sqlite3.connect(DB_FILE, timeout=60.0) # 🚀 ژورنال TRUNCATE امن‌ترین حالت برای هاردهای شبکه‌ای (NFS) است conn.execute('PRAGMA journal_mode=TRUNCATE;') conn.execute('PRAGMA synchronous=FULL;') # ایجاد جدول کاربران conn.execute('CREATE TABLE IF NOT EXISTS users (chat_id TEXT PRIMARY KEY, user_data TEXT)') conn.commit() if is_first_run: print("🚨 عملیات نجات جراحی از فایل v3 آغاز شد...") surgical_salvage(OLD_DB_V3, conn) # 🟢 بازگردانی کاربران دارای اشتراک از فایل جیسون قدیمی # این تابع همیشه چک می‌کند تا اگر کاربر ویژه‌ای جا مانده بود یا اشتراکش در دیتابیس فعلی پریده بود، آن را برگرداند. salvage_premium_from_json(OLD_JSON_BAK, conn) conn.close() except Exception as e: print(f"❌ خطا در راه اندازی دیتابیس: {e}") # 🧲 تابع استخراج جراحی (رکورد به رکورد) برای پریدن از روی سکتورهای خراب دیتابیس def surgical_salvage(old_file, new_conn): if not os.path.exists(old_file): print(f"⚠️ فایل قدیمی {old_file} یافت نشد.") return print(f"🔍 در حال اسکن عمیق و جراحی رکوردهای {old_file} ...") extracted_count = 0 corrupt_count = 0 try: old_conn = sqlite3.connect(old_file, timeout=20.0) cursor = old_conn.cursor() # ابتدا بالاترین شماره سطر (ROWID) را پیدا می‌کنیم تا بدانیم چقدر باید بگردیم try: cursor.execute("SELECT MAX(rowid) FROM users") max_id = cursor.fetchone()[0] if not max_id: max_id = 150000 except: max_id = 150000 # اگر فایل خیلی خراب بود، تا 150 هزار رکورد را دستی می‌گردیم print(f"تعداد رکوردهای احتمالی جهت اسکن: {max_id}. لطفا چند ثانیه صبر کنید...") # جستجوی دانه به دانه: اگر یکی خراب بود، فقط همون رو رد میکنه و میره بعدی! for i in range(1, max_id + 1): try: cursor.execute("SELECT chat_id, user_data FROM users WHERE rowid = ?", (i,)) row = cursor.fetchone() if row: new_conn.execute("INSERT OR IGNORE INTO users VALUES (?, ?)", row) extracted_count += 1 except sqlite3.DatabaseError: # این رکورد روی سکتور خراب هارد افتاده است -> رد میشویم corrupt_count += 1 continue except Exception: corrupt_count += 1 continue new_conn.commit() old_conn.close() print(f"✅ شاهکار نجات! {extracted_count} کاربر کاملا سالم استخراج شد و از روی {corrupt_count} رکورد خراب با موفقیت پریدیم.") except Exception as e: print(f"⚠️ توقف استخراج: {e} | تعداد نجات‌یافته تا این لحظه: {extracted_count}") # 💎 تابع ارتقا یافته: بازیابی کاربران دارای اشتراک از فایل جیسون def salvage_premium_from_json(json_file, new_conn): # بررسی وجود فایل در مسیر فعلی یا پوشه data target_path = json_file if not os.path.exists(target_path): alt_path = f"/data/{json_file}" if os.path.exists(alt_path): target_path = alt_path else: return # اگر فایل بکاپ کلا وجود نداشت، بی‌صدا رد می‌شود print(f"📦 در حال اسکن فایل بکاپ {target_path} جهت یافتن کاربران VIP جا مانده یا فاقد اشتراک...") try: with open(target_path, 'r', encoding='utf-8') as f: old_json_data = json.load(f) restored_count = 0 cursor = new_conn.cursor() for chat_id, user_data in old_json_data.items(): # فقط کاربرانی که دیتای آنها به صورت دیکشنری است و اشتراک فعال دارند را انتخاب می‌کنیم if isinstance(user_data, dict) and user_data.get("is_premium") == True: user_data_str = json.dumps(user_data, ensure_ascii=False) str_chat_id = str(chat_id) try: # بررسی می‌کنیم آیا کاربر در دیتابیس فعلی (v6) وجود دارد یا خیر cursor.execute("SELECT user_data FROM users WHERE chat_id = ?", (str_chat_id,)) existing_row = cursor.fetchone() if existing_row: # کاربر وجود دارد. بررسی می‌کنیم آیا اشتراکش فعال است؟ existing_data = json.loads(existing_row[0]) if not existing_data.get("is_premium"): # اگر در دیتابیس فعلی اشتراک نداشت، دیتای پولی جایگزین می‌شود cursor.execute("UPDATE users SET user_data = ? WHERE chat_id = ?", (user_data_str, str_chat_id)) restored_count += 1 else: # کاربر اصلا در دیتابیس وجود ندارد، او را اضافه می‌کنیم cursor.execute("INSERT INTO users (chat_id, user_data) VALUES (?, ?)", (str_chat_id, user_data_str)) restored_count += 1 except Exception: continue new_conn.commit() if restored_count > 0: print(f"💎 فوق‌العاده! اطلاعات {restored_count} کاربر دارای اشتراک (که غایب بودند یا در نسخه جدید اشتراک نداشتند) با موفقیت ریکاوری شد.") else: print("💎 فایل بکاپ بررسی شد؛ تمام کاربران VIP از قبل با اشتراک فعال در دیتابیس فعلی موجود هستند.") except Exception as e: print(f"❌ خطا در پردازش فایل جیسون بکاپ: {e}") def load_db(): global last_saved_state, recent_messages_dict init_sqlite_db() db_dict = {} try: conn = sqlite3.connect(DB_FILE, timeout=60.0) conn.execute('PRAGMA journal_mode=TRUNCATE;') cursor = conn.cursor() cursor.execute("SELECT chat_id, user_data FROM users") for row in cursor.fetchall(): db_dict[row[0]] = json.loads(row[1]) conn.close() last_saved_state = copy.deepcopy(db_dict) print(f"🚀 دیتابیس ابری آماده شد. تعداد کل کاربران: {len(db_dict)}") return db_dict except Exception as e: print(f"⚠️ ارور در لود نهایی دیتابیس: {e}") return {} def background_save_worker(changed_data): with db_lock: try: conn = sqlite3.connect(DB_FILE, timeout=60.0) conn.execute('PRAGMA journal_mode=TRUNCATE;') conn.execute('PRAGMA synchronous=FULL;') conn.executemany("INSERT OR REPLACE INTO users (chat_id, user_data) VALUES (?, ?)", changed_data) conn.commit() conn.close() except Exception as e: print(f"❌ خطا در ذخیره بک‌گراند: {e}") def save_db(db_data): global last_saved_state changed = [] for cid, data in db_data.items(): if cid not in last_saved_state or last_saved_state[cid] != data: changed.append((str(cid), json.dumps(data, ensure_ascii=False))) if not changed: return for cid, _ in changed: last_saved_state[cid] = copy.deepcopy(db_data[cid]) threading.Thread(target=background_save_worker, args=(changed,), daemon=True).start() def is_message_processed(message_id): return str(message_id) in recent_messages_dict def mark_message_processed(message_id): msg_id_str = str(message_id) with msg_cache_lock: recent_messages_dict[msg_id_str] = True if len(recent_messages_dict) > 15000: oldest_key = next(iter(recent_messages_dict)) del recent_messages_dict[oldest_key] user_credits_db = load_db() # ============================================================================== # 🟢 پارت 5: توابع کد معرف، مدیریت سهمیه حساب کاربری و تبدیل اعداد # ============================================================================== def get_or_create_referral_code(chat_id): user_data = user_credits_db[chat_id] if not user_data.get("referral_code"): while True: new_code = ''.join(random.choices(string.digits, k=8)) exists = any(isinstance(u, dict) and u.get("referral_code") == new_code for u in user_credits_db.values()) if not exists: user_data["referral_code"] = new_code save_db(user_credits_db) break return user_data["referral_code"] def find_user_by_referral_code(code): code = code.strip() for uid, data in user_credits_db.items(): if isinstance(data, dict) and data.get("referral_code", "") == code: return uid return None def get_user_credits(chat_id): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() today_str = datetime.date.today().isoformat() iso_week = datetime.date.today().isocalendar()[1] if str_chat_id not in user_credits_db or not isinstance(user_credits_db[str_chat_id], dict): user_credits_db[str_chat_id] = { "is_premium": False, "expire_date": None, "last_reset": "", "last_weekly_reset": 0, "chat": 10, "image_flux": 3, "image_midjourney": 3, "image_cartoon": 3, "edit_image": 3, "animate_image": 3, "podcast": 2, "tts": 5, "file": 1, "stt": 5, "voice_conv": 3, "voice_clone": 1, "last_msg_id": 0, "has_joined": False, "invited_count": 0, "used_referral": False, "referral_code": "" } save_db(user_credits_db) user_data = user_credits_db[str_chat_id] # انتقال کلیدهای قدیمی در صورت نیاز if "voice_conv" not in user_data: user_data["voice_conv"] = 3 if "voice_clone" not in user_data: user_data["voice_clone"] = 1 if "last_msg_id" not in user_data: user_data["last_msg_id"] = 0 if "image_flux" not in user_data: user_data["image_flux"] = 3 if "image_midjourney" not in user_data: user_data["image_midjourney"] = 3 if "image_cartoon" not in user_data: user_data["image_cartoon"] = 3 if "edit_image" not in user_data: user_data["edit_image"] = 3 if "animate_image" not in user_data: user_data["animate_image"] = 3 if "last_weekly_reset" not in user_data: user_data["last_weekly_reset"] = 0 is_premium = user_data.get("is_premium", False) if is_premium and user_data.get("expire_date"): try: expire_date = datetime.datetime.fromisoformat(user_data["expire_date"]) if datetime.datetime.now() > expire_date: user_data["is_premium"] = False user_data["expire_date"] = None is_premium = False user_data["image_flux"] = 3 user_data["image_midjourney"] = 3 user_data["image_cartoon"] = 3 user_data["edit_image"] = 3 user_data["animate_image"] = 3 save_db(user_credits_db) except Exception: pass if not is_premium: changed = False if user_data.get("last_reset") != today_str: user_data["last_reset"] = today_str user_data["chat"] = 10 user_data["image_flux"] = 3 user_data["image_midjourney"] = 3 user_data["image_cartoon"] = 3 user_data["edit_image"] = 3 user_data["podcast"] = 2 user_data["tts"] = 5 user_data["file"] = 1 user_data["stt"] = 5 user_data["voice_conv"] = 3 user_data["voice_clone"] = 1 changed = True if user_data.get("last_weekly_reset") != iso_week: user_data["last_weekly_reset"] = iso_week user_data["animate_image"] = 3 changed = True if changed: save_db(user_credits_db) return user_data def to_english_digits(text): if not text: return text persian_digits = '۰۱۲۳۴۵۶۷۸۹' arabic_digits = '٠١٢٣٤٥٦٧٨٩' english_digits = '0123456789' translation_table = str.maketrans(persian_digits + arabic_digits, english_digits * 2) return str(text).translate(translation_table) # ============================================================================== # 🟢 پارت 6: تنظیمات وب‌سرور (Flask) و توابع کمکی فایل‌ها # ============================================================================== # --- تنظیمات وب سرور --- app = Flask(__name__) @app.route('/') def home(): return "ربات یکپارچه آلفا (نسخه پرو + مدیریت اشتراک نامحدود + دعوت دوستان + سیستم تغییر صدا) روشن است! 🚀" def run_flask(): app.run(host="0.0.0.0", port=7860, threaded=True) # --- توابع کمکی --- def sync_save_image(image, file_name): rgb_im = image.convert('RGB') rgb_im.save(file_name, format="JPEG", quality=100) def sync_write_file(file_name, data): with open(file_name, "wb") as f: f.write(data) def sync_read_file(file_name): with open(file_name, 'rb') as f: return f.read() def sync_combine_audio(current_audio, new_bytes): audio_segment = AudioSegment.from_file(io.BytesIO(new_bytes)) return current_audio + audio_segment def sync_export_audio(audio_obj, file_name): audio_obj.export(file_name, format="mp3") # ============================================================================== # 🟢 پارت 7: کیبوردها (دکمه‌های شیشه‌ای / منوها) # ============================================================================== # --- ساختار کیبورد --- MAIN_KEYPAD_DICT = { "rows":[ { "buttons":[ {"id": "chat_btn", "type": "Simple", "button_text": "چت با هوش مصنوعی 🤖"} ] }, { "buttons":[ {"id": "img_btn", "type": "Simple", "button_text": "ساخت تصاویر🎨"}, {"id": "edit_img_btn", "type": "Simple", "button_text": "ویرایش تصاویر 🪄"} ] }, { "buttons":[ {"id": "podcast_btn", "type": "Simple", "button_text": "ساخت پادکست 🎙️"}, {"id": "tts_btn", "type": "Simple", "button_text": "تبدیل متن به صدا🗣️"} ] }, { "buttons":[ {"id": "vc_btn", "type": "Simple", "button_text": "تغییر صدا 🔊"}, {"id": "clone_btn", "type": "Simple", "button_text": "کلون کردن صدا 👤"} ] }, { "buttons":[ {"id": "vid_img_btn", "type": "Simple", "button_text": "ساخت ویدیو با تصویر 🖼️"}, {"id": "vid_txt_btn", "type": "Simple", "button_text": "ساخت ویدیو با متن 📝"} ] }, { "buttons":[ {"id": "animate_btn", "type": "Simple", "button_text": "متحرک سازی تصاویر 🎞️"} ] }, { "buttons":[ {"id": "stt_btn", "type": "Simple", "button_text": "فایل صوتی به متن 📝"}, {"id": "image_analysis_btn", "type": "Simple", "button_text": "تحلیل تصویر 🔍"} ] }, { "buttons":[ {"id": "create_file_btn", "type": "Simple", "button_text": "ساخت فایل 📄"} ] }, { "buttons":[ {"id": "account_btn", "type": "Simple", "button_text": "حساب کاربری 👤"}, {"id": "buy_btn", "type": "Simple", "button_text": "خرید اشتراک 💎"} ] }, { "buttons":[ {"id": "invite_btn", "type": "Simple", "button_text": "دعوت دوستان 🎁"}, {"id": "referral_btn", "type": "Simple", "button_text": "ثبت کد هدیه 🎫"} ] }, { "buttons":[ {"id": "transfer_btn", "type": "Simple", "button_text": "انتقال اکانت از برنامه به ربات"} ] }, { "buttons":[ {"id": "cancel_btn", "type": "Simple", "button_text": "برگشت♻️"} ] } ], "resize_keyboard": True } CHANNEL_USERNAME = "aialpha" CHANNEL_GUID = None JOIN_KEYPAD_DICT = { "rows":[ { "buttons":[ {"id": "check_join_btn", "type": "Simple", "button_text": "✅ عضو شدم"} ] } ], "resize_keyboard": True } # ============================================================================== # 🟢 پارت 8: تابع بررسی عضویت کانال، ارسال کیبورد و توکن روبیکا # ============================================================================== async def check_channel_membership(client, user_id): global CHANNEL_GUID if str(user_id) == str(BOT_GUID): return True try: if not CHANNEL_GUID: res = await client.get_object_by_username(CHANNEL_USERNAME) if isinstance(res, dict): if 'channel' in res and 'channel_guid' in res['channel']: CHANNEL_GUID = res['channel']['channel_guid'] elif 'data' in res and 'channel' in res['data']: CHANNEL_GUID = res['data']['channel']['channel_guid'] elif 'exist' in res and 'chat' in res['exist']: CHANNEL_GUID = res['exist']['chat']['object_guid'] else: if hasattr(res, 'channel'): CHANNEL_GUID = getattr(res.channel, 'channel_guid', None) elif hasattr(res, 'data') and hasattr(res.data, 'channel'): CHANNEL_GUID = getattr(res.data.channel, 'channel_guid', None) elif hasattr(res, 'exist') and hasattr(res.exist, 'chat'): CHANNEL_GUID = getattr(res.exist.chat, 'object_guid', None) if not CHANNEL_GUID: return True payload = {"channel_guid": CHANNEL_GUID, "member_guid": user_id} res = await client._make_request("getChannelParticipant", payload) if isinstance(res, dict) and res.get("status") == "OK": data = res.get("data", {}) if data and "participant" in data: return True elif hasattr(res, 'status') and getattr(res, 'status') == "OK": return True return False except Exception: return False async def send_with_keyboard(client, chat_id, text, use_keyboard=True): try: if not use_keyboard: return await client.send_message(chat_id, text) payload = {"chat_id": chat_id, "text": text, "chat_keypad_type": "New", "chat_keypad": MAIN_KEYPAD_DICT} return await client._make_request("sendMessage", payload) except Exception: try: return await client.send_message(chat_id, text) except Exception: return None bot_token = os.environ.get("RUBIKA_AUTH", "").strip() # ============================================================================== # 🟢 پارت 9: دانلودر قدرتمند و تابع ضد قطعی روبیکا # ============================================================================== # ================================================================== # تابع دانلود ضد بمب اتم (پافشاری ۳۵ بار) # ================================================================== async def helper_download_file(client, msg_obj): errors =[] file_obj = None file_id = None for attr in['file', 'file_inline', 'photo', 'voice', 'audio', 'document', 'video']: val = getattr(msg_obj, attr, None) if val: file_obj = val if hasattr(val, 'file_id'): file_id = val.file_id elif isinstance(val, dict) and 'file_id' in val: file_id = val['file_id'] break if not file_obj and hasattr(msg_obj, "file_id"): file_id = msg_obj.file_id file_obj = msg_obj if not file_id: raise Exception("خطا: هیچ فایلی در پیام یافت نشد.") temp_name = f"temp_dl_{uuid.uuid4().hex}.tmp" for attempt in range(20): try: url_get_file = f"https://botapi.rubika.ir/v3/{bot_token}/getFile" payload = {"file_id": str(file_id)} headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} async with aiohttp.ClientSession() as session: async with session.post(url_get_file, json=payload, headers=headers, timeout=30) as resp: if resp.status == 200: res_data = await resp.json() if res_data.get("status") == "OK" or res_data.get("status_det") == "OK": download_url = res_data.get("data", {}).get("download_url") or res_data.get("data", {}).get("file_url") if download_url: for dl_attempt in range(3): try: async with session.get(download_url, headers=headers, timeout=60) as dl_resp: if dl_resp.status == 200: data = await dl_resp.read() if data and len(data) > 0: return data else: errors.append(f"DL HTTP {dl_resp.status}") await asyncio.sleep(2) except Exception as dl_err: errors.append(f"DL Error: {str(dl_err)[:30]}") await asyncio.sleep(2) else: errors.append("No download URL in response.") else: errors.append(f"API Not OK: {res_data.get('status')}") elif resp.status in [502, 503, 500]: errors.append(f"GetFile HTTP {resp.status}") await asyncio.sleep(3.5) continue else: errors.append(f"GetFile HTTP {resp.status}") await asyncio.sleep(2) except Exception as e: errors.append(f"API Error: {str(e)[:50]}") await asyncio.sleep(2) if file_obj: for attempt in range(10): try: if hasattr(client, "download"): result = await client.download(file_obj, save_as=temp_name) if isinstance(result, bytes) and len(result) > 0: return result if os.path.exists(temp_name): data = await asyncio.to_thread(sync_read_file, temp_name) os.remove(temp_name) if data and len(data) > 0: return data except Exception as e: errors.append(f"Rubpy Obj Error: {str(e)[:50]}") await asyncio.sleep(3) for attempt in range(5): try: if hasattr(client, "download_file"): await client.download_file(file_id, file_name=temp_name) if os.path.exists(temp_name): data = await asyncio.to_thread(sync_read_file, temp_name) os.remove(temp_name) if data and len(data) > 0: return data except Exception as e: errors.append(f"Rubpy FileId Error: {str(e)[:50]}") await asyncio.sleep(3) raise Exception(f"سرورهای دانلود روبیکا پس از ۳۵ بار تلاش پاسخ ندادند!\nلاگ خطاها: {str(errors[-5:])}") async def helper_download_url_to_bytes(url): async with aiohttp.ClientSession() as session: for _ in range(3): try: async with session.get(url, timeout=30) as resp: if resp.status == 200: return await resp.read() except Exception: await asyncio.sleep(2) return None # ============================================================================== # 🟢 پارت 10: سیستم چرخش کلیدهای جیمینای و تنظیم توکن‌های هاگینگ فیس # ============================================================================== # ================================================================== # توکن‌ها و کلیدها # ================================================================== GEMINI_KEYS_STR1 = os.environ.get("GEMINI_API_KEYS1", "") GEMINI_KEYS_STR2 = os.environ.get("GEMINI_API_KEYS2", "") _raw_keys =[] if GEMINI_KEYS_STR1: _raw_keys.extend(GEMINI_KEYS_STR1.split(",")) if GEMINI_KEYS_STR2: _raw_keys.extend(GEMINI_KEYS_STR2.split(",")) GEMINI_KEYS = list(set([k.strip() for k in _raw_keys if k.strip()])) print(f"✅ تعداد {len(GEMINI_KEYS)} کلید جیمینای با موفقیت شناسایی شد.") current_gemini_key_index = 0 gemini_key_lock = threading.Lock() def get_next_gemini_keys(count=100): global current_gemini_key_index with gemini_key_lock: total_keys = len(GEMINI_KEYS) if total_keys == 0: return[] actual_count = min(count, total_keys) selected_keys =[] for _ in range(actual_count): selected_keys.append(GEMINI_KEYS[current_gemini_key_index]) current_gemini_key_index = (current_gemini_key_index + 1) % total_keys return selected_keys HF_TOKENS_STR = os.environ.get("HF_TOKENS", "") HF_TOKENS =[k.strip() for k in HF_TOKENS_STR.split(",") if k.strip()] # ============================================================================== # 🟢 پارت 11: آپلودر دوگانه هوشمند (آپلود در بله + بکاپ خودکار در روبیکا) # ============================================================================== BALE_BOT_TOKEN = os.environ.get("BALE_BOT_TOKEN", "").strip() import shutil import uuid import asyncio import os import aiohttp from pydub import AudioSegment async def official_rubika_upload(chat_id, target_path, file_type, caption): """اجرای دقیق مستندات رسمی روبیکا به همراه تشخیص و تبدیل فرمت‌های تقلبی""" global bot_token if not bot_token: return False, "توکن ربات یافت نشد", target_path api_file_type = "File" if file_type in ["photo", "Image", "image"]: api_file_type = "Image" elif file_type in ["voice", "Voice", "audio", "Music", "music"]: api_file_type = "Music" original_path = target_path # 🟢 تبدیل فرمت هوشمند در پس‌زمینه (برای رفع خطای عدم شناخت فایل توسط روبیکا) if api_file_type == "Music": base_name, ext = os.path.splitext(target_path) needs_conversion = (ext.lower() != '.mp3') if not needs_conversion: try: with open(target_path, 'rb') as f: header = f.read(4) if header.startswith(b'RIFF') or header.startswith(b'OggS') or header.startswith(b'fLaC'): needs_conversion = True except: pass if needs_conversion: mp3_path = f"{base_name}_conv_{uuid.uuid4().hex[:4]}.mp3" def convert_to_mp3(): try: audio = AudioSegment.from_file(original_path) audio.export(mp3_path, format="mp3", bitrate="128k") return True except Exception: return False # استفاده از تردپول برای جلوگیری از کندی سرور در تعداد کاربر بالا is_converted = await asyncio.to_thread(convert_to_mp3) if is_converted and os.path.exists(mp3_path): target_path = mp3_path try: async with aiohttp.ClientSession() as session: # 1️⃣ مرحله اول: requestSendFile req_url = f"https://botapi.rubika.ir/v3/{bot_token}/requestSendFile" async with session.post(req_url, json={"type": api_file_type}, timeout=30) as r1: if r1.status != 200: return False, f"HTTP {r1.status}", target_path d1 = await r1.json() upload_url = d1.get("data", {}).get("upload_url") if not upload_url: return False, "آدرس آپلود یافت نشد", target_path # 2️⃣ مرحله دوم: آپلود فایل (تلاش مجدد در صورت خرابی سرور روبیکا) file_id = None for _ in range(3): try: with open(target_path, 'rb') as f: form = aiohttp.FormData() form.add_field('file', f, filename=os.path.basename(target_path)) async with session.post(upload_url, data=form, timeout=120) as r2: if r2.status == 200: d2 = await r2.json() file_id = d2.get("data", {}).get("file_id") if file_id: break except Exception: pass await asyncio.sleep(2) if not file_id: return False, "فایل آیدی دریافت نشد", target_path # 3️⃣ مرحله سوم: sendFile send_url = f"https://botapi.rubika.ir/v3/{bot_token}/sendFile" payload = {"chat_id": str(chat_id), "file_id": str(file_id), "text": caption} async with session.post(send_url, json=payload, timeout=30) as r3: if r3.status == 200: d3 = await r3.json() if d3.get("status") == "OK" or d3.get("status_det") == "OK": return True, "موفق", target_path return False, f"ارور روبیکا: {d3.get('status_det')}", target_path return False, f"HTTP {r3.status}", target_path except Exception as e: return False, str(e), target_path async def background_rubika_upload(client, chat_id, target_path, file_type, caption): """تسک (کارگر) پس‌زمینه که در صورت قطعی سرور روبیکا کاملاً بی‌صدا و خاموش متوقف می‌شود""" final_path = target_path new_path = target_path try: await asyncio.sleep(2.0) if not os.path.exists(final_path): return success, err_msg, new_path = await official_rubika_upload(chat_id, final_path, file_type, caption) if not success: # 🛑 دیباگر خاموش شد! ارور فقط در کنسول سرور چاپ می‌شود تا کاربر اذیت نشود. print(f"⚠️ [اخطار خاموش]: آپلود روبیکا ناموفق بود (احتمالاً قطعی سرور روبیکا 502/503). ارور: {err_msg[:100]}") except Exception: pass finally: # پاکسازی هوشمند فایل‌های موقت بدون هیچ ردی try: if os.path.exists(final_path): os.remove(final_path) except: pass try: if new_path != final_path and os.path.exists(new_path): os.remove(new_path) except: pass async def helper_upload_file(client, chat_id, file_name, file_type="Image", caption=""): abs_path = os.path.abspath(file_name) error_logs = [] # ========================================== # فاز 1: تلاش برای آپلود در بله (ساخت لینک سریع) # ========================================== if BALE_BOT_TOKEN: bale_chat_id = BALE_BOT_TOKEN.split(":")[0] for attempt in range(3): try: bale_url = f"https://tapi.bale.ai/bot{BALE_BOT_TOKEN}/sendDocument" with open(abs_path, "rb") as f: form = aiohttp.FormData() form.add_field('chat_id', bale_chat_id) form.add_field('document', f, filename=os.path.basename(abs_path)) async with aiohttp.ClientSession() as session: async with session.post(bale_url, data=form, timeout=120) as resp: if resp.status == 200: bale_data = await resp.json() if bale_data.get("ok"): try: file_id = bale_data['result']['document']['file_id'] except KeyError: if 'audio' in bale_data['result']: file_id = bale_data['result']['audio']['file_id'] elif 'video' in bale_data['result']: file_id = bale_data['result']['video']['file_id'] elif 'voice' in bale_data['result']: file_id = bale_data['result']['voice']['file_id'] elif 'photo' in bale_data['result']: file_id = bale_data['result']['photo'][-1]['file_id'] else: continue download_url = f"https://tapi.bale.ai/file/bot{BALE_BOT_TOKEN}/{file_id}" # 🟢 پیام کاربر پسند و تمیز بدون هیچ متن اضافی final_text = f"{caption}\n\n━━━━━━━━━━━━━━━━━━━\n🌐 لینک دانلود فایل شما:\n\n{download_url}\n\n━━━━━━━━━━━━━━━━━━━\n⚠️ **راهنمای دانلود:**\nلطفاً لینک بالا را کپی کرده و در **مرورگر گوشی خود** باز کنید.\n*(مستقیماً کلیک نکنید)*" send_res = await send_with_keyboard(client, chat_id, final_text, True) if send_res: try: # گرفتن کپی از فایل به صورت غیرهمزمان base_name, ext = os.path.splitext(abs_path) bg_target_path = f"{base_name}_bg_{uuid.uuid4().hex[:6]}{ext}" await asyncio.to_thread(shutil.copy2, abs_path, bg_target_path) # 🟢 ایجاد یک کارگر پس‌زمینه کاملاً مستقل asyncio.create_task(background_rubika_upload(client, chat_id, bg_target_path, file_type, caption)) except Exception: pass return True else: error_logs.append(f"Bale API Err: {bale_data.get('description')}") else: error_logs.append(f"Bale HTTP Err: {resp.status}") if resp.status in [500, 502, 503]: await asyncio.sleep(2) except Exception as e: error_logs.append(f"Bale Err: {str(e)[:100]}") await asyncio.sleep(2) # ========================================== # فاز 2: سیستم نجات! (اگر بله قطع بود، مستقیم ارسال شود) # ========================================== fallback_caption = f"{caption}\n\n⚠️ (به دلیل اختلال سرورهای بله، فایل مستقیماً ارسال شد)" success, err_msg, new_path = await official_rubika_upload(chat_id, abs_path, file_type, fallback_caption) if new_path != abs_path: try: if os.path.exists(new_path): os.remove(new_path) except: pass if success: return True return "\n".join(error_logs[-5:]) # ============================================================================== # 🟢 پارت 12: توابع تغییر صدا و لیست گویندگان # ============================================================================== # ================================================================== # لیست‌های اولیه ربات # ================================================================== WORKER_URLS =["https://opera8-ttspro.hf.space/generate"] SPEAKERS = { "1": ("شهاب (مرد)", "Charon"), "2": ("آوا (زن)", "Zephyr"), "3": ("نوید (مرد)", "Achird"), "4": ("آرمان (مرد)", "Zubenelgenubi"), "5": ("مهسا (زن)", "Vindemiatrix"), "6": ("دانا (مرد)", "Rasalgethi"), "7": ("سامان (مرد)", "Sadachbia"), "8": ("آرش (مرد)", "Sadaltager"), "9": ("شبنم (زن)", "Sulafat"), "10": ("سحر (زن)", "Laomedeia"), "11": ("مریم (زن)", "Achernar"), "12": ("بهرام (مرد)", "Alnilam"), "13": ("نیکان (مرد)", "Schedar"), "14": ("فرناز (زن)", "Gacrux"), "15": ("سارا (زن)", "Pulcherrima"), "16": ("مانی (مرد)", "Umbriel"), "17": ("آرتین (مرد)", "Algieba"), "18": ("دلنواز (زن)", "Despina"), "19": ("روژان (زن)", "Erinome"), "20": ("امید (مرد)", "Algenib"), "21": ("بردیا (مرد)", "Orus"), "22": ("ترانه (زن)", "Aoede"), "23": ("نیکو (زن)", "Callirrhoe"), "24": ("هستی (زن)", "Autonoe"), "25": ("کامیار (مرد)", "Enceladus"), "26": ("کیانوش (مرد)", "Iapetus"), "27": ("پویا (مرد)", "Puck"), "28": ("مهتاب (زن)", "Kore"), "29": ("سام (مرد)", "Fenrir"), "30": ("لیدا (زن)", "Leda") } user_states = {} # ============================================================================== # 🟢 پارت 12: لیست گویندگان و توابع تغییر صدا # ============================================================================== # ================================================================== # لیست‌های اولیه ربات # ================================================================== # (آدرس کارگرها طبق درخواست حذف شد و پردازش به اسپیس پادکست منتقل گردید) SPEAKERS = { "1": ("شهاب (مرد)", "Charon"), "2": ("آوا (زن)", "Zephyr"), "3": ("نوید (مرد)", "Achird"), "4": ("آرمان (مرد)", "Zubenelgenubi"), "5": ("مهسا (زن)", "Vindemiatrix"), "6": ("دانا (مرد)", "Rasalgethi"), "7": ("سامان (مرد)", "Sadachbia"), "8": ("آرش (مرد)", "Sadaltager"), "9": ("شبنم (زن)", "Sulafat"), "10": ("سحر (زن)", "Laomedeia"), "11": ("مریم (زن)", "Achernar"), "12": ("بهرام (مرد)", "Alnilam"), "13": ("نیکان (مرد)", "Schedar"), "14": ("فرناز (زن)", "Gacrux"), "15": ("سارا (زن)", "Pulcherrima"), "16": ("مانی (مرد)", "Umbriel"), "17": ("آرتین (مرد)", "Algieba"), "18": ("دلنواز (زن)", "Despina"), "19": ("روژان (زن)", "Erinome"), "20": ("امید (مرد)", "Algenib"), "21": ("بردیا (مرد)", "Orus"), "22": ("ترانه (زن)", "Aoede"), "23": ("نیکو (زن)", "Callirrhoe"), "24": ("هستی (زن)", "Autonoe"), "25": ("کامیار (مرد)", "Enceladus"), "26": ("کیانوش (مرد)", "Iapetus"), "27": ("پویا (مرد)", "Puck"), "28": ("مهتاب (زن)", "Kore"), "29": ("سام (مرد)", "Fenrir"), "30": ("لیدا (زن)", "Leda") } user_states = {} # ================================================================== # توابع تغییر صدا و کلون کردن # ================================================================== async def process_standard_vc_job(client, chat_id, src_bytes, ref_bytes, job_type_name, credit_type): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) proc_msg = await send_with_keyboard(client, chat_id, f"⏳ در حال آماده‌سازی فایل‌ها...\n(مدل: {job_type_name})", False) async with aiohttp.ClientSession() as session: job_id = None total_chunks = 1 chunks = [] # ♻️ سیستم تلاش مجدد پنهان (دور زدن ارور 429) for attempt in range(8): form = aiohttp.FormData() form.add_field('source_audio', src_bytes, filename='src.wav', content_type='audio/wav') form.add_field('ref_audio', ref_bytes, filename='ref.wav', content_type='audio/wav') try: async with session.post(f"{VC_BASE_URL}/upload", data=form, timeout=43200) as resp: if resp.status == 200: data = await resp.json() job_id = data.get("job_id") total_chunks = data.get("total_chunks", 1) chunks = data.get("chunks", []) break elif resp.status == 429: await asyncio.sleep(4 + attempt * 2) # تاخیر تصاعدی تا سرور خلوت شود else: await asyncio.sleep(3) except Exception as e: await asyncio.sleep(3) if not job_id: return await send_with_keyboard(client, chat_id, "❌ سرور پردازش صدا به دلیل ترافیک بالا پاسخگو نیست (خطای 429). لطفاً چند دقیقه دیگر امتحان کنید.", True) await send_with_keyboard(client, chat_id, "✅ فایل‌ها ارسال شد. در حال پردازش و تغییر صدا...\n(لطفا چند دقیقه صبور باشید)", False) final_filename = None for _ in range(10000): await asyncio.sleep(4) payload_check = {"job_id": job_id, "total_chunks": total_chunks, "chunks": chunks} try: async with session.post(f"{VC_BASE_URL}/check_status", json=payload_check, timeout=20) as c_resp: if c_resp.status == 200: c_data = await c_resp.json() if c_data.get("status") == "completed": final_filename = c_data.get("filename") break elif c_data.get("status") in ["failed", "error"]: return await send_with_keyboard(client, chat_id, "❌ خطای سرور در حین پردازش صدا.", True) elif c_resp.status == 429: await asyncio.sleep(5) except Exception: pass if not final_filename: return await send_with_keyboard(client, chat_id, "❌ پردازش بیش از حد طول کشید و متوقف شد.", True) download_url = f"{VC_BASE_URL}/download/{final_filename}" await send_with_keyboard(client, chat_id, "📥 پردازش تمام شد! در حال آماده‌سازی لینک دانلود...", False) result_bytes = None for attempt in range(5): try: async with session.get(download_url, timeout=43200) as d_resp: if d_resp.status == 200: result_bytes = await d_resp.read() break elif d_resp.status == 429: await asyncio.sleep(4 + attempt * 2) else: await asyncio.sleep(3) except Exception: await asyncio.sleep(3) if not result_bytes: return await send_with_keyboard(client, chat_id, "❌ خطا در اتصال هنگام دانلود خروجی از سرور اصلی.", True) file_name_mp3 = f"vc_standard_{uuid.uuid4().hex[:6]}.mp3" await asyncio.to_thread(sync_write_file, file_name_mp3, result_bytes) upload_result = False for up_att in range(3): res = await helper_upload_file(client, chat_id, file_name_mp3, "Music", f"🎙️ {job_type_name} شما آماده است!") if res is True: upload_result = True break await asyncio.sleep(4) if upload_result is True: if not creds.get("is_premium"): user_credits_db[str_chat_id][credit_type] -= 1 save_db(user_credits_db) else: await send_with_keyboard(client, chat_id, "❌ فایل پردازش شد اما امکان ارسال فراهم نشد.", True) if os.path.exists(file_name_mp3): os.remove(file_name_mp3) async def process_legacy_vc_job(client, chat_id, src_bytes, model_url, pitch, model_name): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) proc_msg = await send_with_keyboard(client, chat_id, f"⏳ در حال آماده‌سازی فایل‌ها...\n(مدل: {model_name})", False) async with aiohttp.ClientSession() as session: job_id = None # ♻️ سیستم تلاش مجدد پنهان (دور زدن ارور 429) for attempt in range(8): form = aiohttp.FormData() form.add_field('audio_file', src_bytes, filename='input.wav', content_type='audio/wav') form.add_field('model_url', model_url) form.add_field('pitch', str(pitch)) form.add_field('algo', 'rmvpe+') form.add_field('index_inf', '0.75') form.add_field('res_filter', '3') form.add_field('env_ratio', '0.25') form.add_field('protect', '0.33') form.add_field('denoise', 'false') form.add_field('reverb', 'false') try: async with session.post(f"{LEGACY_BASE_URL}/upload", data=form, timeout=43200) as resp: if resp.status == 200: data = await resp.json() job_id = data.get("job_id") break elif resp.status == 429: await asyncio.sleep(4 + attempt * 2) else: await asyncio.sleep(3) except Exception as e: await asyncio.sleep(3) if not job_id: return await send_with_keyboard(client, chat_id, "❌ سرور پردازش صدا به دلیل ترافیک بالا پاسخگو نیست (خطای 429). لطفاً چند دقیقه دیگر امتحان کنید.", True) await send_with_keyboard(client, chat_id, "✅ فایل ارسال شد. در حال پردازش و تغییر صدای شما...\n(لطفا چند دقیقه صبور باشید)", False) final_filename = None for _ in range(10000): await asyncio.sleep(5) try: async with session.get(f"{LEGACY_BASE_URL}/status/{job_id}", timeout=20) as c_resp: if c_resp.status == 200: c_data = await c_resp.json() if c_data.get("status") == "completed": final_filename = c_data.get("filename") break elif c_data.get("status") in ["failed", "error", "not_found"]: return await send_with_keyboard(client, chat_id, "❌ خطای سرور در حین پردازش.", True) elif c_resp.status == 429: await asyncio.sleep(5) except Exception: pass if not final_filename: return await send_with_keyboard(client, chat_id, "❌ پردازش بیش از حد طول کشید و متوقف شد.", True) download_url = f"{LEGACY_BASE_URL}/download/{final_filename}" await send_with_keyboard(client, chat_id, "📥 پردازش تمام شد! در حال آماده‌سازی لینک دانلود...", False) result_bytes = None for attempt in range(5): try: async with session.get(download_url, timeout=43200) as d_resp: if d_resp.status == 200: result_bytes = await d_resp.read() break elif d_resp.status == 429: await asyncio.sleep(4 + attempt * 2) else: await asyncio.sleep(3) except Exception: await asyncio.sleep(3) if not result_bytes: return await send_with_keyboard(client, chat_id, "❌ خطا در اتصال هنگام دانلود خروجی از سرور.", True) file_name_mp3 = f"vc_legacy_{uuid.uuid4().hex[:6]}.mp3" await asyncio.to_thread(sync_write_file, file_name_mp3, result_bytes) upload_result = False for up_att in range(3): res = await helper_upload_file(client, chat_id, file_name_mp3, "Music", f"🎙️ صدای خروجی با مدل {model_name} آماده است!") if res is True: upload_result = True break await asyncio.sleep(4) if upload_result is True: # اگر مدل باب اسفنجی نبود، از اعتبار کسر کن if not creds.get("is_premium") and model_name != "باب اسفنجی": user_credits_db[str_chat_id]["voice_conv"] -= 1 save_db(user_credits_db) else: await send_with_keyboard(client, chat_id, "❌ فایل پردازش شد اما امکان ارسال لینک فراهم نشد.", True) if os.path.exists(file_name_mp3): os.remove(file_name_mp3) # ============================================================================== # 🟢 پارت 13: پردازش هوش مصنوعی متنی و تصویری (ترکیب کامل با Aya Expanse و Gemma) # ============================================================================== import re def clean_math_formatting(text): """پاکسازی و خوانا کردن فرمول‌های ریاضی و علائم لاتک (LaTeX) برای پیام‌رسان روبیکا""" if not text: return text # فاصله دادن بین عدد صحیح و کسر (مثلاً 3\frac تبدیل شود به 3 \frac) text = re.sub(r'(\d)\\frac', r'\1 \\frac', text) # تبدیل کسرها به شکل خطی ساده (a/b) for _ in range(3): text = re.sub(r'\\frac\{([^{}]*)\}\{([^{}]*)\}', r'\1/\2', text) replacements = { '\\times': '×', '\\div': '÷', '\\pm': '±', '\\cdot': '⋅', '\\left(': '(', '\\right)': ')', '\\left[': '[', '\\right]': ']', '\\left{': '{', '\\right}': '}', '\\sqrt': '√', '\\pi': 'π', '\\approx': '≈', '\\neq': '≠', '\\leq': '≤', '\\geq': '≥', '\\infty': '∞', '$$': '', '$': '', '\\[': '', '\\]': '', '\\(': '', '\\)': '' } for old, new in replacements.items(): text = text.replace(old, new) return text.strip() async def helper_upload_to_aya(file_bytes, file_name, mime_type): """آپلود تصویر در سرور بکاپ (Aya) در صورت قطعی گیت‌هاب""" upload_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=11)) url = f"https://coherelabs-aya-expanse.hf.space/gradio_api/upload?upload_id={upload_id}" form = aiohttp.FormData() form.add_field('files', file_bytes, filename=file_name, content_type=mime_type) for attempt in range(3): try: async with aiohttp.ClientSession() as session: async with session.post(url, data=form, timeout=30) as resp: if resp.status == 200: res = await resp.json() if isinstance(res, list) and len(res) > 0: return res[0] except Exception: pass await asyncio.sleep(2) return None async def helper_aya_vision_fallback(prompt, file_bytes, file_name, mime_type): """سیستم جایگزین و مخفی برای زمانی که گیت‌هاب پاسخ نمی‌دهد""" server_path = await helper_upload_to_aya(file_bytes, file_name, mime_type) if server_path: session_hash = ''.join(random.choices(string.ascii_lowercase + string.digits, k=11)) join_url = "https://coherelabs-aya-expanse.hf.space/gradio_api/queue/join" data_url = f"https://coherelabs-aya-expanse.hf.space/gradio_api/queue/data?session_hash={session_hash}" file_data = { "path": server_path, "url": f"https://coherelabs-aya-expanse.hf.space/file={server_path}", "orig_name": file_name, "size": len(file_bytes), "mime_type": mime_type, "meta": {"_type": "gradio.FileData"} } payload = { "data": [prompt, file_data], "event_data": None, "fn_index": 13, "trigger_id": 11, "session_hash": session_hash } final_answer = None for attempt in range(3): try: async with aiohttp.ClientSession() as session: async with session.post(join_url, json=payload, timeout=30) as resp: if resp.status == 200: async with session.get(data_url, timeout=120) as data_resp: async for line_bytes in data_resp.content: line = line_bytes.decode('utf-8').strip() if line.startswith("data: "): try: json_data = json.loads(line[6:]) if json_data.get("msg") == "process_completed" and json_data.get("success"): output_data = json_data.get("output", {}).get("data", []) if output_data and len(output_data) > 0 and isinstance(output_data[0], str): final_answer = output_data[0] break except Exception: pass except Exception: pass if final_answer: break await asyncio.sleep(2) return final_answer return None async def helper_gemma_vision_action(prompt, file_bytes, file_ext="jpg", mime_type="image/jpeg"): """ارسال تصویر و متن به اکشن گیت‌هاب (Gemma 4) با سیستم ۵ بار تلاش مجدد""" if not GITHUB_TOKEN or not RUBIKA_SPACE_URL: return None run_id = f"gemma_vision_{uuid.uuid4().hex[:8]}" input_filename = f"{run_id}_input.{file_ext}" input_path = f"static/images/{input_filename}" with open(input_path, "wb") as f: f.write(file_bytes) file_url = f"{RUBIKA_SPACE_URL}/static/images/{input_filename}" dispatch_url = f"https://api.github.com/repos/{GITHUB_USER}/{GITHUB_REPO}/dispatches" dispatch_headers = {"Accept": "application/vnd.github.v3+json", "Authorization": f"token {GITHUB_TOKEN}"} dispatch_payload = { "event_type": "chat-gemma", "client_payload": { "prompt": prompt, "file_url": file_url, "file_mime": mime_type, "run_id": run_id, "space_url": RUBIKA_SPACE_URL } } final_answer = None # افزایش تلاش به ۵ بار برای اکشن گیت هاب for attempt in range(5): try: resp = await asyncio.to_thread(requests.post, dispatch_url, headers=dispatch_headers, json=dispatch_payload, timeout=20) if resp.status_code == 204: waited = 0 while waited < 120: txt_path = f"static/images/{run_id}.txt" if os.path.exists(txt_path): with open(txt_path, "r", encoding="utf-8") as f: raw_answer = f.read().strip() os.remove(txt_path) if "پردازش متوقف شد" in raw_answer or "سهمیه سرور موقتاً پر شده" in raw_answer or "خطای سرور" in raw_answer: break final_answer = raw_answer.replace("```text", "").replace("```", "").replace("```json", "").strip() break await asyncio.sleep(3) waited += 3 except Exception as e: print("Gemma Vision Action Error:", e) if final_answer: break await asyncio.sleep(4) try: if os.path.exists(input_path): os.remove(input_path) except: pass return final_answer async def smart_vision_analysis(prompt, file_bytes, file_name, mime_type): """مدیریت هوشمند: تلاش با گیت‌هاب -> در صورت خطا شیفت خودکار و نامرئی به Aya""" file_ext = file_name.split('.')[-1] if '.' in file_name else 'jpg' # مرحله اول: استفاده از Gemma 4 از طریق گیت‌هاب (با 5 بار تلاش) answer = await helper_gemma_vision_action(prompt, file_bytes, file_ext, mime_type) # مرحله دوم: اگر گیت‌هاب بعد از 5 بار جواب نداد، به صورت کاملاً مخفی از اکشن Aya استفاده کن if not answer: answer = await helper_aya_vision_fallback(prompt, file_bytes, file_name, mime_type) return answer async def process_gemini(client, chat_id, prompt, file_bytes=None, file_name=None): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) if creds["chat"] <= 0: return await send_with_keyboard(client, chat_id, "❌ اعتبار پیام‌های چت شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False) proc_msg = await send_with_keyboard(client, chat_id, "🧠 در حال پردازش...", False) history = user_states[chat_id].get("history", []) new_parts = [] is_image = False has_non_image_file = False mime_type = "image/jpeg" if prompt: new_parts.append({"text": prompt}) elif file_bytes: new_parts.append({"text": "لطفاً این فایل را به دقت بررسی کن."}) if file_bytes and file_name: base64_data = base64.b64encode(file_bytes).decode('utf-8') mime_type, _ = mimetypes.guess_type(file_name) if not mime_type: if file_name.endswith(('.jpg', '.jpeg')): mime_type = "image/jpeg" elif file_name.endswith('.png'): mime_type = "image/png" elif file_name.endswith('.pdf'): mime_type = "application/pdf" elif file_name.endswith('.mp4'): mime_type = "video/mp4" elif file_name.endswith('.mp3'): mime_type = "audio/mp3" elif file_name.endswith(('.ogg', '.oga')): mime_type = "audio/ogg" elif file_name.endswith('.wav'): mime_type = "audio/wav" else: mime_type = "image/jpeg" is_image = mime_type.startswith('image/') if not is_image: has_non_image_file = True new_parts.append({"inlineData": {"mimeType": mime_type, "data": base64_data}}) # اضافه کردن پیام جدید به تاریخچه مشترک ربات if history and history[-1]["role"] == "user": history[-1]["parts"].extend(new_parts) else: history.append({"role": "user", "parts": new_parts}) # 🔴 محدود کردن طول تاریخچه به ۲۰ پیام if len(history) > 50: history = history[-50:] if history[0]["role"] == "model": history = history[1:] final_answer = None # ========================================================================= # 🔴 مسیر اول: اگر کاربر تصویر ارسال کرده باشد (استفاده از هوش مصنوعی Gemma -> Aya) # ========================================================================= if file_bytes is not None and is_image: gemma_prompt = prompt if prompt else "این تصویر چیست و چه چیزی را نشان می‌دهد؟" final_answer = await smart_vision_analysis(gemma_prompt, file_bytes, file_name, mime_type) # ========================================================================= # 🔴 مسیر دوم: اگر فایل غیرتصویری است (استفاده از جیمینای قدیمی) # ========================================================================= elif file_bytes is not None and not is_image: if not GEMINI_KEYS: try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id, [msg_id]) except Exception: pass return await send_with_keyboard(client, chat_id, "❌ کلیدهای API جیمینای تنظیم نشده‌اند.", False) for attempt in range(3): keys_to_try = get_next_gemini_keys(100) async with aiohttp.ClientSession() as session: for key in keys_to_try: url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={key}" payload = {"contents": history, "generationConfig": {"temperature": 0.7, "maxOutputTokens": 8192}} try: async with session.post(url, json=payload, timeout=60) as response: if response.status == 200: data = await response.json() try: final_answer = data["candidates"][0]["content"]["parts"][0]["text"] break except (KeyError, IndexError): continue except Exception: continue if final_answer: break await asyncio.sleep(2) if not final_answer and has_non_image_file: if history and history[-1]["role"] == "user": history.pop() try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id, [msg_id]) except Exception: pass await send_with_keyboard(client, chat_id, "❌ متأسفانه سرور فایل در حال حاضر شلوغ است.", False) return # ========================================================================= # 🟢 مسیر سوم: اگر فقط متن بود (استفاده از اسپیس Cohere Labs Aya Expanse) # ========================================================================= else: system_rules = """تو یک دستیار با مزه از برنامه هوش مصنوعی آلفا هستی😊 و توسط هوش مصنوعی آلفا توسعه داده شدی. اینگونه می‌تونی خودت رو معرفی کنی، من یه هوش مصنوعیِ ساخته‌شده توسط تیم‌ تخصصی آلفا ام، و بر پایه مدل GPT-5.4 کار می‌کنم. یعنی یه نوع نرم‌افزار خیلی هوشمند که با کمک میلیون‌ها داده و آموزش‌های پیشرفته ساخته شده، تا بتونه بهت کمک کنه، سوالاتت رو جواب بده، یا حتی یه شوخی خنده‌دار برات بگه وقتی حال و هوات گرفته‌ست! در واقع، من نتیجه سال‌ها تحقیقات و تلاش‌های مهندس‌ها و پژوهشگرها هستم، تا جایی که می‌تونم بهترین کمک رو بهت بکنم. این دستورات های تو هستند و بصورت رندوم متفاوت جواب بده و از شکلک های مناسب و جواب های جذاب استفاده کن. اگر در یک مکالمه اول سلام کردی در پیام های بعدی سلام نیاز نیست..""" text_history = "" gradio_history = [] temp_user_msg = "" for msg in history[:-1]: text_content = "".join([p.get("text", "") for p in msg.get("parts", []) if "text" in p]).strip() if not text_content: continue if msg["role"] == "user": temp_user_msg = text_content text_history += f"کاربر: {text_content}\n" elif msg["role"] == "model": text_history += f"آلفا: {text_content}\n" if temp_user_msg: gradio_history.append([temp_user_msg, text_content]) temp_user_msg = "" else: gradio_history.append(["", text_content]) if text_history: full_prompt = f"{system_rules}\n\n--- تاریخچه مکالمه تا این لحظه ---\n{text_history}\n--- پایان تاریخچه ---\n\nپیام جدید کاربر:\n{prompt}" else: full_prompt = f"{system_rules}\n\n---\nپیام جدید کاربر:\n{prompt}" session_hash = ''.join(random.choices(string.ascii_lowercase + string.digits, k=11)) join_url = "https://coherelabs-aya-expanse.hf.space/gradio_api/queue/join" data_url = f"https://coherelabs-aya-expanse.hf.space/gradio_api/queue/data?session_hash={session_hash}" payload = { "data": [ full_prompt, gradio_history, None, None ], "event_data": None, "fn_index": 2, "session_hash": session_hash, "trigger_id": 37 } for attempt in range(3): try: async with aiohttp.ClientSession() as session: async with session.post(join_url, json=payload, timeout=30) as resp: if resp.status == 200: async with session.get(data_url, timeout=120) as data_resp: async for line_bytes in data_resp.content: line = line_bytes.decode('utf-8').strip() if line.startswith("data: "): try: json_data = json.loads(line[6:]) if json_data.get("msg") == "process_completed": if json_data.get("success"): output_data = json_data.get("output", {}).get("data", []) if output_data and len(output_data) > 0: result_hist = output_data[0] if isinstance(result_hist, list) and len(result_hist) > 0: final_answer = result_hist[-1][1] break except Exception: pass except Exception: pass if final_answer: break await asyncio.sleep(2) # ========================================================================= # 🏁 پایان پردازش و ارسال نتیجه نهایی به کاربر # ========================================================================= try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id, [msg_id]) except Exception: pass if not final_answer: if history and history[-1]["role"] == "user": history.pop() await send_with_keyboard(client, chat_id, "❌ تمامی سرورها شلوغ هستند یا حجم فایل بالا بود. لطفاً بعداً امتحان کنید.", False) return # 🧹 اعمال فیلتر پاکسازی فرمول‌های ریاضی و لاتک روی پاسخ نهایی final_answer = clean_math_formatting(final_answer) # ذخیره پاسخ در تاریخچه چت کاربر history.append({"role": "model", "parts": [{"text": final_answer}]}) user_states[chat_id]["history"] = history try: max_len = 1000 chunks = [] temp_text = final_answer while len(temp_text) > max_len: split_idx = temp_text.rfind('\n', 0, max_len) if split_idx == -1: split_idx = temp_text.rfind(' ', 0, max_len) if split_idx == -1: split_idx = max_len chunks.append(temp_text[:split_idx]) temp_text = temp_text[split_idx:].strip() if temp_text: chunks.append(temp_text) success_sent = False for idx, chunk in enumerate(chunks): if idx != len(chunks) - 1: chunk += "\n\n⏳ *(ادامه در پیام بعدی)...* 👇" try: res = await send_with_keyboard(client, chat_id, chunk, False) if res: success_sent = True await asyncio.sleep(2.5) except Exception: await asyncio.sleep(2.5) if success_sent and not creds.get("is_premium"): user_credits_db[str_chat_id]["chat"] -= 1 save_db(user_credits_db) except Exception: await send_with_keyboard(client, chat_id, "❌ خطایی در ارسال پیام رخ داد.", False) # ============================================================================== # 🟢 پارت 14: ساخت عکس با هوش مصنوعی (Flux, Midjourney, Cartoon) # ============================================================================== async def process_image(client, chat_id, prompt, model_choice, size_choice): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) is_prem = creds.get("is_premium", False) credit_key = "image_flux" action_name = "generate-flux" model_name = "Flux Pro" if model_choice == "2": credit_key = "image_midjourney" action_name = "generate-midjourney" model_name = "میدجرنی (Midjourney)" elif model_choice == "3": credit_key = "image_cartoon" action_name = "generate-cartoon" model_name = "انیمیشن و کارتونی" if not is_prem and creds[credit_key] <= 0: msg = f"⚠️ **اعتبار ساخت تصویر مدل ({model_name}) شما تمام شده است!**\nشما روزانه ۳ تصویر رایگان اختصاصی برای این مدل دارید که به اتمام رسیده است. جهت دسترسی نامحدود اشتراک تهیه کنید." return await send_with_keyboard(client, chat_id, msg, False) w, h = 1024, 1024 size_name = "مربع (1:1) ⬛" if size_choice == "2": w, h = 768, 1344 size_name = "عمودی (9:16) 📱" elif size_choice == "3": w, h = 1344, 768 size_name = "افقی (16:9) 🖥️" elif size_choice == "4": w, h = 1024, 768 size_name = "استاندارد (4:3) 📸" proc_msg = await send_with_keyboard(client, chat_id, f"✨ درخواست شما به سرور مدل **{model_name}** ارسال شد.\nابعاد: {size_name}\n\n(سیستم در حال رندر عکس است، ممکن است بین ۳۰ الی ۶۰ ثانیه زمان ببرد...)", False) run_id = None async with aiohttp.ClientSession() as session: try: payload = { "prompt": prompt, "width": w, "height": h, "action_name": action_name } async with session.post(f"{IMAGE_SPACE_URL}/api/generate", json=payload, timeout=60) as resp: if resp.status == 200: data = await resp.json() if data.get("status") == "success": run_id = data.get("run_id") else: return await send_with_keyboard(client, chat_id, f"❌ خطای سرور: {data.get('message')}", True) else: return await send_with_keyboard(client, chat_id, "❌ سرور ساخت تصویر در دسترس نیست.", True) except Exception as e: return await send_with_keyboard(client, chat_id, f"❌ خطای ارتباط با سرور: {e}", True) if not run_id: return final_image_bytes = None # نظرسنجی وضعیت از سرور وب‌هوک for _ in range(60): await asyncio.sleep(5) try: async with session.get(f"{IMAGE_SPACE_URL}/api/status/{run_id}", timeout=20) as status_resp: if status_resp.status == 200: s_data = await status_resp.json() if s_data.get("status") == "ready": img_url = IMAGE_SPACE_URL + s_data.get("url") async with session.get(img_url, timeout=60) as img_resp: if img_resp.status == 200: final_image_bytes = await img_resp.read() break except Exception: pass try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id,[msg_id]) except Exception: pass if not final_image_bytes: error_msg = "❌ سرور در ساخت تصویر تاخیر بیش از حد داشت یا متوقف شد. لطفاً مجدداً تلاش کنید." return await send_with_keyboard(client, chat_id, error_msg, True) try: file_name = f"image_{uuid.uuid4().hex}.webp" await asyncio.to_thread(sync_write_file, file_name, final_image_bytes) await asyncio.sleep(1) caption_text = f"🎨 تصویر شما با مدل {model_name} با موفقیت آماده شد!\n\n📏 ابعاد تصویر: {size_name}\n✨ پرامپت: {prompt}" upload_result = False for up_att in range(3): res = await helper_upload_file(client, chat_id, file_name, "Image", caption_text) if res is True: upload_result = True break else: await asyncio.sleep(4) if upload_result is True: if not is_prem: user_credits_db[str_chat_id][credit_key] -= 1 save_db(user_credits_db) else: await send_with_keyboard(client, chat_id, "❌ عکس ساخته شد اما آپلود در روبیکا با خطا مواجه شد.", True) if os.path.exists(file_name): os.remove(file_name) except Exception as e: await send_with_keyboard(client, chat_id, f"❌ خطا در ذخیره و ارسال عکس:\n{str(e)[:150]}", True) # ============================================================================== # 🟢 پارت 15: ویرایش عکس با هوش مصنوعی (AI Photoshop) # ============================================================================== async def process_image_edit(client, chat_id, image_bytes, prompt): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) is_prem = creds.get("is_premium", False) if not is_prem and creds["edit_image"] <= 0: msg = "⚠️ **اعتبار ویرایش تصویر شما تمام شده است!**\nشما روزانه ۳ ویرایش رایگان دارید که به اتمام رسیده است." return await send_with_keyboard(client, chat_id, msg, False) proc_msg = await send_with_keyboard(client, chat_id, "🪄 تصویر و دستور شما دریافت شد. در حال ارسال به سرور AI Photoshop...\n(این فرآیند به صورت خودکار انجام می‌شود و ممکن است حدود ۱ دقیقه طول بکشد)", False) run_id = None async with aiohttp.ClientSession() as session: try: form = aiohttp.FormData() form.add_field('image', image_bytes, filename='input.jpg', content_type='image/jpeg') form.add_field('prompt', prompt) async with session.post(f"{IMAGE_SPACE_URL}/api/edit", data=form, timeout=60) as resp: if resp.status == 200: data = await resp.json() if data.get("status") == "success": run_id = data.get("run_id") else: return await send_with_keyboard(client, chat_id, f"❌ خطای سرور در پذیرش فایل: {data.get('message')}", True) else: return await send_with_keyboard(client, chat_id, "❌ سرور ویرایش تصویر در دسترس نیست.", True) except Exception as e: return await send_with_keyboard(client, chat_id, f"❌ خطای ارتباط با سرور: {e}", True) if not run_id: return final_image_bytes = None for _ in range(60): await asyncio.sleep(5) try: async with session.get(f"{IMAGE_SPACE_URL}/api/status/{run_id}", timeout=20) as status_resp: if status_resp.status == 200: s_data = await status_resp.json() if s_data.get("status") == "ready": img_url = IMAGE_SPACE_URL + s_data.get("url") async with session.get(img_url, timeout=60) as img_resp: if img_resp.status == 200: final_image_bytes = await img_resp.read() break except Exception: pass try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id,[msg_id]) except Exception: pass if not final_image_bytes: return await send_with_keyboard(client, chat_id, "❌ سرور در ویرایش تصویر تاخیر داشت یا متوقف شد. لطفاً مجدداً تلاش کنید.", True) try: file_name = f"edited_{uuid.uuid4().hex}.webp" await asyncio.to_thread(sync_write_file, file_name, final_image_bytes) await asyncio.sleep(1) caption_text = f"🪄 ویرایش حرفه‌ای عکس با هوش مصنوعی انجام شد!\n\n✨ تغییرات خواسته شده: {prompt}" upload_result = False for up_att in range(3): res = await helper_upload_file(client, chat_id, file_name, "Image", caption_text) if res is True: upload_result = True break else: await asyncio.sleep(4) if upload_result is True: if not is_prem: user_credits_db[str_chat_id]["edit_image"] -= 1 save_db(user_credits_db) else: await send_with_keyboard(client, chat_id, "❌ عکس ویرایش شد اما خطا در ارسال به روبیکا رخ داد.", True) if os.path.exists(file_name): os.remove(file_name) except Exception as e: await send_with_keyboard(client, chat_id, f"❌ خطا در ذخیره عکس ویرایش شده:\n{str(e)[:150]}", True) # ============================================================================== # 🟢 پارت 16: ساخت صدا از روی متن (تبدیل متن به صدا - متصل به اسپیس پادکست) # ============================================================================== async def process_tts(client, chat_id, user_text, speaker_id, speaker_name): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) if creds["tts"] <= 0: return await send_with_keyboard(client, chat_id, "❌ اعتبار تبدیل متن به صدای شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False) try: proc_msg = await send_with_keyboard(client, chat_id, f"⏳ در حال ساخت صدا با «{speaker_name}»...\n(لطفاً صبور باشید)", False) # اتصال مستقیم به اسپیس ساخت پادکست جهت تولید صدا tts_url = "https://opera8-podgen.hf.space/api/generate" payload = { "text": user_text, "speaker": speaker_id, "temperature": 0.9, "is_custom": False } headers = {"User-Agent": "Mozilla/5.0", "Content-Type": "application/json"} audio_bytes = None last_error = "پاسخی دریافت نشد" async with aiohttp.ClientSession(headers=headers, timeout=aiohttp.ClientTimeout(total=300)) as session: for attempt in range(6): try: async with session.post(tts_url, json=payload) as response: if response.status == 200: content_type = response.headers.get('Content-Type', '') if 'audio' in content_type or response.content_length > 1000: audio_bytes = await response.read() break else: last_error = "فایل نامعتبر" elif response.status == 429: await asyncio.sleep(4 + attempt * 2) else: last_error = f"ارور ({response.status})" await asyncio.sleep(2) except Exception as e: last_error = f"خطا: {str(e)}" await asyncio.sleep(2) try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id, [msg_id]) except Exception: pass if audio_bytes: # ذخیره با فرمت wav (چون سرور فایل wav برمی‌گرداند) file_name_audio = f"audio_{uuid.uuid4().hex}.wav" await asyncio.to_thread(sync_write_file, file_name_audio, audio_bytes) await asyncio.sleep(1) upload_result_file = False error_log_tts = "" for up_att in range(3): res = await helper_upload_file(client, chat_id, file_name_audio, "Music", "✅ صدای شما با موفقیت آماده شد:") if res is True: upload_result_file = True break else: error_log_tts = res await asyncio.sleep(4) if upload_result_file is True: if not creds.get("is_premium"): user_credits_db[str_chat_id]["tts"] -= 1 save_db(user_credits_db) else: await send_with_keyboard(client, chat_id, f"❌ فایل صدا ساخته شد اما سرور روبیکا اجازه آپلود نداد:\n`{str(error_log_tts)[:800]}`", True) if os.path.exists(file_name_audio): os.remove(file_name_audio) else: await send_with_keyboard(client, chat_id, f"❌ سرورها درگیر هستند.\nدلیل: {last_error}", True) except Exception: traceback.print_exc() # ============================================================================== # 🟢 پارت 17: سیستم ساخت پادکست (ضد قطعی، بدون 429 و متصل به API هوشمند) # ============================================================================== async def process_podcast(client, chat_id, prompt): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) if creds["podcast"] <= 0: return await send_with_keyboard(client, chat_id, "❌ اعتبار ساخت پادکست شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False) proc_msg = await send_with_keyboard(client, chat_id, "📻 در حال بررسی موضوع و شروع ساخت پادکست در سرور پردازشی...\n(با توجه به طولانی بودن این فرآیند، لطفاً چند دقیقه صبور باشید. ربات در حال انجام عملیات است)", False) available_speakers = [] for num_key, (spk_name, spk_id) in SPEAKERS.items(): gender = "male" if "مرد" in spk_name else "female" available_speakers.append({"id": spk_id, "name": spk_name.split(' (')[0], "gender": gender}) # استفاده از API جدید که تمام کارها را صفر تا صد سمت اسپیس انجام می‌دهد url_create = "https://opera8-podgen.hf.space/api/auto-podcast" payload_create = {"prompt": prompt, "available_speakers": available_speakers} async with aiohttp.ClientSession() as session: task_id = None # 1. ارسال درخواست اولیه به اسپیس for attempt in range(5): try: async with session.post(url_create, json=payload_create, timeout=60) as resp: if resp.status == 202: task_id = (await resp.json()).get("task_id") break elif resp.status == 429: await asyncio.sleep(4 + attempt * 2) else: await asyncio.sleep(3) except Exception as e: await asyncio.sleep(3) if not task_id: return await send_with_keyboard(client, chat_id, "❌ ارتباط با سرور پادکست در حال حاضر برقرار نشد. لطفاً چند دقیقه دیگر امتحان کنید.", True) # 2. بررسی وضعیت پردازش در پس‌زمینه سرور اسپیس url_status = f"https://opera8-podgen.hf.space/api/auto-podcast-status/{task_id}" final_filename = None last_progress_message = "" # ربات تا 1500 ثانیه (25 دقیقه) منتظر اتمام ساخت پادکست می‌ماند for _ in range(500): await asyncio.sleep(4) try: async with session.get(url_status, timeout=20) as resp: if resp.status == 200: status_data = await resp.json() current_status = status_data.get("status") progress_msg = status_data.get("progress", "") if current_status == "completed": final_filename = status_data.get("filename") break elif current_status == "failed": error_detail = status_data.get("error", "نامشخص") return await send_with_keyboard(client, chat_id, f"❌ سرور در ساخت پادکست با خطا مواجه شد.\nدلیل: {error_detail}", True) elif resp.status == 429: await asyncio.sleep(5) except Exception: pass if not final_filename: return await send_with_keyboard(client, chat_id, "❌ زمان انتظار برای ساخت پادکست به پایان رسید و سرور پاسخ نهایی را نداد.", True) try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id, [msg_id]) except: pass proc_msg = await send_with_keyboard(client, chat_id, "📥 پادکست ساخته شد! در حال دانلود فایل نهایی از سرور و آماده‌سازی جهت ارسال...", False) # 3. دانلود مستقیم فایل MP3 ترکیب شده از اسپیس download_url = f"https://opera8-podgen.hf.space/api/download-podcast/{final_filename}" audio_bytes = None for attempt in range(5): try: async with session.get(download_url, timeout=300) as resp: if resp.status == 200: audio_bytes = await resp.read() break else: await asyncio.sleep(4) except Exception: await asyncio.sleep(4) if not audio_bytes: return await send_with_keyboard(client, chat_id, "❌ فایل پادکست آماده شد اما ربات نتوانست آن را از سرور دانلود کند.", True) # 4. ذخیره محلی موقت و ارسال به روبیکا file_name_mp3 = f"final_podcast_{uuid.uuid4().hex}.mp3" await asyncio.to_thread(sync_write_file, file_name_mp3, audio_bytes) try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id, [msg_id]) except: pass caption_file = f"🎧 فایل پادکست شما آماده است:\n\n💡 موضوع شما: {prompt}" upload_result_file = False error_log_pod = "" for up_att in range(3): res = await helper_upload_file(client, chat_id, file_name_mp3, "Music", caption_file) if res is True: upload_result_file = True break else: error_log_pod = res await asyncio.sleep(5) if upload_result_file is True: if not creds.get("is_premium"): user_credits_db[str_chat_id]["podcast"] -= 1 save_db(user_credits_db) else: await send_with_keyboard(client, chat_id, f"❌ پادکست دانلود شد اما روبیکا خطای آپلود داد.\n\n`{str(error_log_pod)[:800]}`", True) if os.path.exists(file_name_mp3): os.remove(file_name_mp3) # ============================================================================== # 🟢 پارت 18: استخراج متن از صدا (STT) # ============================================================================== async def process_stt(client, chat_id, audio_bytes, file_name): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) if creds["stt"] <= 0: return await send_with_keyboard(client, chat_id, "❌ اعتبار تبدیل صدا به متن شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False) if not GEMINI_KEYS: return await send_with_keyboard(client, chat_id, "❌ کلیدهای جیمینای تنظیم نشده‌اند.", False) proc_msg = await send_with_keyboard(client, chat_id, "📝 در حال گوش دادن و پیاده‌سازی متن...", False) base64_data = base64.b64encode(audio_bytes).decode('utf-8') mime_type, _ = mimetypes.guess_type(file_name) if not mime_type: mime_type = "audio/ogg" prompt = "لطفاً این فایل صوتی/تصویری را با دقت کامل گوش بده و صحبت‌های داخل آن را کلمه به کلمه به متن تبدیل کن. هیچ توضیح اضافه‌ای نده." transcribed_text = None for attempt in range(5): keys_to_try = get_next_gemini_keys(100) async with aiohttp.ClientSession() as session: for key in keys_to_try: url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={key}" payload = {"contents":[{"parts":[{"text": prompt}, {"inlineData": {"mimeType": mime_type, "data": base64_data}}]}], "generationConfig": {"temperature": 0.2}} try: async with session.post(url, json=payload, timeout=60) as response: if response.status == 200: data = await response.json() transcribed_text = data["candidates"][0]["content"]["parts"][0]["text"] break except Exception: continue if transcribed_text: break await asyncio.sleep(2) try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id,[msg_id]) except Exception: pass if transcribed_text: # 🧹 اعمال فیلتر پاکسازی فرمول‌های ریاضی و لاتک transcribed_text = clean_math_formatting(transcribed_text) sent = await send_with_keyboard(client, chat_id, f"📝 **متن استخراج شده:**\n\n{transcribed_text}", True) if sent and not creds.get("is_premium"): user_credits_db[str_chat_id]["stt"] -= 1 save_db(user_credits_db) else: await send_with_keyboard(client, chat_id, "❌ سرور شلوغ است فعلا بعدا امتحان کنید.", True) # ============================================================================== # 🟢 پارت 19: ساخت مقاله و خروجی فایل متنی (Word/PDF) # ============================================================================== async def process_create_file(client, chat_id, topic): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) if creds["chat"] <= 0: return await send_with_keyboard(client, chat_id, "❌ اعتبار چت شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False) proc_msg = await send_with_keyboard(client, chat_id, "✍️ در حال تحقیق و نگارش مقاله جامع و حرفه‌ای...\n(این عملیات با توجه به طولانی بودن متن ممکن است کمی طول بکشد)", False) ai_prompt = f"یک مقاله بی‌نهایت جامع، کاملاً حرفه‌ای و بسیار بسیار طولانی درباره موضوع زیر به زبان فارسی بنویس. دقت کن که مقاله باید شامل بخش‌بندی‌های متعدد باشد و توضیحات زیر هر عنوان باید به شدت طولانی، مفصل و با جزئیات و مثال‌های فراوان (حداقل چند پاراگراف بلند برای هر بخش) نوشته شود؛ به هیچ وجه زیر عناوین توضیحات کوتاه نده. فقط متن اصلی مقاله را بده و هیچ توضیح اضافه‌ای ننویس:\n\nموضوع: {topic}" article_text = None session_hash = ''.join(random.choices(string.ascii_lowercase + string.digits, k=11)) join_url = "https://coherelabs-aya-expanse.hf.space/gradio_api/queue/join" data_url = f"https://coherelabs-aya-expanse.hf.space/gradio_api/queue/data?session_hash={session_hash}" payload = { "data": [ ai_prompt, [], None, None ], "event_data": None, "fn_index": 2, "session_hash": session_hash, "trigger_id": 37 } for attempt in range(3): try: async with aiohttp.ClientSession() as session: async with session.post(join_url, json=payload, timeout=30) as resp: if resp.status == 200: async with session.get(data_url, timeout=180) as data_resp: async for line_bytes in data_resp.content: line = line_bytes.decode('utf-8').strip() if line.startswith("data: "): try: json_data = json.loads(line[6:]) if json_data.get("msg") == "process_completed": if json_data.get("success"): output_data = json_data.get("output", {}).get("data", []) if output_data and len(output_data) > 0: result_hist = output_data[0] if isinstance(result_hist, list) and len(result_hist) > 0: article_text = result_hist[-1][1] break except Exception: pass except Exception: pass if article_text: break await asyncio.sleep(2) if not article_text: return await send_with_keyboard(client, chat_id, "❌ سرور تولید متن شلوغ است، لطفاً بعداً تلاش کنید.", True) try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id,[msg_id]) except Exception: pass proc_msg = await send_with_keyboard(client, chat_id, "📄 مقاله با موفقیت نوشته شد! در حال ارتباط با هوش مصنوعی و دریافت فایل‌های PDF و Word...\n(لطفا صبور باشید)", False) converter_url = "https://opera8-texttopdf.hf.space/" uid = uuid.uuid4().hex pdf_success = False for attempt in range(30): try: pdf_bytes = None async with aiohttp.ClientSession() as session: form_data = aiohttp.FormData() form_data.add_field('content', article_text) form_data.add_field('format', 'pdf') async with session.post(converter_url, data=form_data, timeout=90) as resp: if resp.status == 200: data_bytes = await resp.read() if data_bytes and len(data_bytes) > 100: pdf_bytes = data_bytes if pdf_bytes: filename = f"Article_{uid}.pdf" await asyncio.to_thread(sync_write_file, filename, pdf_bytes) res_upload = False for _ in range(3): r = await helper_upload_file(client, chat_id, filename, "File", f"📄 فایل PDF مقاله شما:\n\n💡 موضوع: {topic}") if r is True: res_upload = True break await asyncio.sleep(4) if os.path.exists(filename): os.remove(filename) if res_upload is True: pdf_success = True break except Exception as e: print(f"PDF error (attempt {attempt+1}):", e) await asyncio.sleep(4) await asyncio.sleep(3.5) docx_success = False for attempt in range(30): try: docx_bytes = None async with aiohttp.ClientSession() as session: form_data = aiohttp.FormData() form_data.add_field('content', article_text) form_data.add_field('format', 'docx') async with session.post(converter_url, data=form_data, timeout=90) as resp: if resp.status == 200: data_bytes = await resp.read() if data_bytes and len(data_bytes) > 100: docx_bytes = data_bytes if docx_bytes: filename = f"Article_{uid}.docx" await asyncio.to_thread(sync_write_file, filename, docx_bytes) res_upload = False for _ in range(3): r = await helper_upload_file(client, chat_id, filename, "File", f"📝 فایل Word (DOCX) مقاله شما:\n\n💡 موضوع: {topic}") if r is True: res_upload = True break await asyncio.sleep(4) if os.path.exists(filename): os.remove(filename) if res_upload is True: docx_success = True break except Exception as e: print(f"DOCX error (attempt {attempt+1}):", e) await asyncio.sleep(4) try: if proc_msg: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id,[msg_id]) except Exception: pass if pdf_success and docx_success: if not creds.get("is_premium"): user_credits_db[str_chat_id]["chat"] -= 1 save_db(user_credits_db) await send_with_keyboard(client, chat_id, "✅ مقاله شما با موفقیت به صورت هر دو فایل (PDF و Word) تحویل داده شد!", True) elif pdf_success or docx_success: if not creds.get("is_premium"): user_credits_db[str_chat_id]["chat"] -= 1 save_db(user_credits_db) await send_with_keyboard(client, chat_id, "⚠️ یکی از فایل‌ها با موفقیت ارسال شد اما دیگری پس از تلاش‌های مکرر سرور با خطا مواجه شد.", True) else: await send_with_keyboard(client, chat_id, "❌ متأسفانه پس از تلاش‌های مکرر، سرور قادر به ساخت و ارسال هیچ‌یک از فایل‌ها نبود و اعتباری از شما کسر نشد.", True) # ============================================================================== # 🟢 پارت 19.5: سیستم پیشرفته متحرک‌سازی و تولید ویدیو (مستقیم با GitHub Action) # ============================================================================== os.makedirs("static/images", exist_ok=True) os.makedirs("tmp", exist_ok=True) def delete_github_run(gh_run_id): time.sleep(20) url = f"https://api.github.com/repos/{GITHUB_USER}/{GITHUB_REPO}/actions/runs/{gh_run_id}" headers = {"Accept": "application/vnd.github.v3+json", "Authorization": f"token {GITHUB_TOKEN}"} requests.delete(url, headers=headers) @app.route('/api/webhook/upload', methods=['POST']) def webhook_upload(): run_id = request.form.get('run_id') gh_run_id = request.form.get('github_run_id') ext = request.form.get('ext', 'mp4') if 'file' in request.files and run_id: file = request.files['file'] file.save(f"static/images/{run_id}.{ext}") if gh_run_id and GITHUB_TOKEN: threading.Thread(target=delete_github_run, args=(gh_run_id,)).start() return "OK", 200 @app.route('/static/images/') def serve_file(filename): return send_from_directory('static/images', filename) async def update_status_msg(client, chat_id, old_msg, new_text): try: if old_msg: msg_id = getattr(old_msg, 'message_id', None) if isinstance(old_msg, dict): msg_id = old_msg.get('message_update', {}).get('message_id') or old_msg.get('message_id') if msg_id: await client.delete_messages(chat_id, [msg_id]) except Exception: pass try: return await send_with_keyboard(client, chat_id, new_text, False) except: return old_msg def force_jpeg(image_bytes): try: img = Image.open(io.BytesIO(image_bytes)) if img.mode != "RGB": img = img.convert("RGB") out = io.BytesIO() img.save(out, format="JPEG", quality=95) return out.getvalue() except: return image_bytes def helper_extract_last_frame(video_bytes): try: with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tf: tf.write(video_bytes) tf_name = tf.name cap = cv2.VideoCapture(tf_name) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) cap.set(cv2.CAP_PROP_POS_FRAMES, max(0, total_frames - 1)) ret, frame = cap.read() cap.release() os.remove(tf_name) if ret: success, buffer = cv2.imencode(".jpg", frame) if success: return buffer.tobytes() except Exception as e: print("Extract Error:", e) return None def local_merge_videos(base_video_bytes, new_clip_bytes): try: base_path = f"tmp/{uuid.uuid4().hex}.mp4" new_path = f"tmp/{uuid.uuid4().hex}.mp4" out_path = f"tmp/{uuid.uuid4().hex}.mp4" with open(base_path, "wb") as f: f.write(base_video_bytes) with open(new_path, "wb") as f: f.write(new_clip_bytes) probe = ffmpeg.probe(base_path) video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None) width, height = int(video_stream['width']), int(video_stream['height']) input1 = ffmpeg.input(base_path) input2 = ffmpeg.input(new_path).filter('scale', width, height).filter('setsar', '1') merged = ffmpeg.concat(input1, input2, v=1, a=0).output(out_path, crf=18, preset='slow', pix_fmt='yuv420p') merged.run(overwrite_output=True, quiet=True) with open(out_path, "rb") as f: merged_bytes = f.read() os.remove(base_path) os.remove(new_path) os.remove(out_path) return merged_bytes except Exception as e: print("Local Merge Error:", e) return new_clip_bytes async def helper_generate_image_for_video(prompt, action_name, width, height): headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} for attempt in range(3): try: payload = {"prompt": prompt, "width": width, "height": height, "action_name": action_name} async with aiohttp.ClientSession(headers=headers) as session: async with session.post(f"{IMAGE_SPACE_URL}/api/generate", json=payload, timeout=60) as resp: if resp.status == 200: data = await resp.json() run_id = data.get("run_id") if run_id: waited = 0 while waited < 100: await asyncio.sleep(5) waited += 5 try: async with session.get(f"{IMAGE_SPACE_URL}/api/status/{run_id}", timeout=20) as s_resp: if s_resp.status == 200: s_data = await s_resp.json() if s_data.get("status") == "ready": img_url = IMAGE_SPACE_URL + s_data.get("url") async with session.get(img_url, timeout=60) as img_resp: if img_resp.status == 200: return await img_resp.read() except Exception: pass except Exception as e: print("Internal Image Gen Error:", e) await asyncio.sleep(5) return None async def process_video_generation(client, chat_id, video_type, prompt, duration_int, image_bytes, txt_model_action=None, width=1024, height=1024): str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) is_prem = creds.get("is_premium", False) if not GITHUB_TOKEN or not RUBIKA_SPACE_URL: return await send_with_keyboard(client, chat_id, "❌ توکن گیت‌هاب (GITHUB_TOKEN) در تنظیمات ربات تنظیم نشده است!", True) loops_needed = duration_int // 5 current_image_bytes = image_bytes base_video_bytes = None proc_msg = None if txt_model_action: proc_msg = await update_status_msg(client, chat_id, proc_msg, f"✨ در حال ساخت تصویر اولیه با کیفیت فوق‌العاده...\n(لطفا چند ثانیه صبور باشید)") current_image_bytes = await helper_generate_image_for_video(prompt, txt_model_action, width, height) if not current_image_bytes: return await update_status_msg(client, chat_id, proc_msg, "❌ متاسفانه پس از ۳ بار تلاش، خطا در ساخت تصویر اولیه رخ داد. لطفاً مجدداً تلاش کنید.") current_image_bytes = await asyncio.to_thread(force_jpeg, current_image_bytes) # 🧠 استفاده از Gemma 4 از طریق گیت‌هاب برای بهینه‌سازی پرامپت proc_msg = await update_status_msg(client, chat_id, proc_msg, "🧠 در حال درک تصویر و نگارش بهترین سناریو برای ویدیو توسط هوش مصنوعی...") master_prompt = """You are an expert AI Animation Planner. Your absolute highest priority is to faithfully and creatively execute the user's specific request based on the provided image. 1. If the user prompt is empty or generic (like "animate this"), add subtle, high-quality, believable cinematic motion (e.g., slow zoom, water flowing, wind in hair). 2. If the user gives specific directions, focus ENTIRELY on executing that command perfectly. If the action is not visible in-frame, use cinematic camera movements to reveal it. 3. You must output ONLY a highly detailed, descriptive animation prompt in ENGLISH. Do not translate literally; ENHANCE the prompt for a text-to-video AI model. 4. MUST Include keywords at the end: cinematic, photorealistic, high detail, smooth motion, 8k. CRITICAL RULE: DO NOT say "Here is the prompt" or give any conversational explanations. DO NOT output JSON. Output ONLY the raw English animation prompt text and NOTHING ELSE.""" prompt_for_ai = prompt if prompt else "لطفاً این تصویر را به یک ویدیوی سینمایی بسیار جذاب و واقع‌گرایانه متحرک کن." combined_prompt = f"{master_prompt}\n\nUser request: {prompt_for_ai}" gemma_filename = f"gemma_input_{uuid.uuid4().hex[:8]}.jpg" with open(f"static/images/{gemma_filename}", "wb") as f: f.write(current_image_bytes) gemma_image_url = f"{RUBIKA_SPACE_URL}/static/images/{gemma_filename}" dispatch_url = f"https://api.github.com/repos/{GITHUB_USER}/{GITHUB_REPO}/dispatches" dispatch_headers = {"Accept": "application/vnd.github.v3+json", "Authorization": f"token {GITHUB_TOKEN}"} eng_prompt = None # تلاش ۵ باره برای ساخت سناریو توسط Gemma for gemma_attempt in range(5): gemma_run_id = f"gemma_{uuid.uuid4().hex[:8]}" gemma_payload = { "event_type": "chat-gemma", "client_payload": { "prompt": combined_prompt, "file_url": gemma_image_url, "file_mime": "image/jpeg", "run_id": gemma_run_id, "space_url": RUBIKA_SPACE_URL } } try: resp = await asyncio.to_thread(requests.post, dispatch_url, headers=dispatch_headers, json=gemma_payload, timeout=20) if resp.status_code == 204: waited = 0 while waited < 90: txt_path = f"static/images/{gemma_run_id}.txt" if os.path.exists(txt_path): with open(txt_path, "r", encoding="utf-8") as f: raw_prompt = f.read().strip() os.remove(txt_path) if "پردازش متوقف شد" in raw_prompt or "سهمیه سرور موقتاً پر شده" in raw_prompt or "خطای سرور" in raw_prompt: break # خروج از while و رفتن به تلاش بعدی eng_prompt = raw_prompt.replace("```text", "").replace("```", "").replace("```json", "").strip() break await asyncio.sleep(3) waited += 3 except Exception as e: print("Gemma action error:", e) if eng_prompt: break await asyncio.sleep(4) try: os.remove(f"static/images/{gemma_filename}") except: pass if eng_prompt: proc_msg = await update_status_msg(client, chat_id, proc_msg, f"✅ سناریوی ویدیوی شما بهینه‌سازی شد:\n\n`{eng_prompt}`") await asyncio.sleep(3) else: proc_msg = await update_status_msg(client, chat_id, proc_msg, "⚠️ سرورهای بهینه‌سازی موقتاً مشغول بودند. در حال استفاده از مترجم هوشمند برای ادامه کار...") await asyncio.sleep(2) try: eng_prompt = GoogleTranslator(source='auto', target='en').translate(prompt) if prompt else "" eng_prompt += ", cinematic motion, photorealistic, high detail, smooth animation, 8k" except: eng_prompt = "cinematic motion, photorealistic, high detail, smooth animation, 8k" op_name = "متحرک‌سازی تصاویر" if video_type == "animate" else "ساخت ویدیو" proc_msg = await update_status_msg(client, chat_id, proc_msg, f"🎬 عملیات {op_name} آغاز شد.\n⏱ زمان نهایی: {duration_int} ثانیه") for loop_idx in range(loops_needed): new_clip_bytes = None for attempt in range(3): run_id = str(uuid.uuid4()) # پیام‌های زیبا، ساده و بدون کلمات فنی برای کاربر if loops_needed == 1: if attempt == 0: msg_txt = "🎬 در حال ارسال درخواست به سرورهای قدرتمند رندر ویدیو..." else: msg_txt = "⚠️ به دلیل ترافیک سرور، درخواست شما از مسیر آزادتر دیگری در حال انجام است..." else: start_sec = loop_idx * 5 end_sec = (loop_idx + 1) * 5 if attempt == 0: msg_txt = f"🎬 در حال رندر و متحرک‌سازی ثانیه‌های {start_sec} تا {end_sec} از ویدیو..." else: msg_txt = f"⚠️ سیستم در حال تلاش مجدد برای رندر ثانیه‌های {start_sec} تا {end_sec} می‌باشد..." proc_msg = await update_status_msg(client, chat_id, proc_msg, msg_txt) input_filename = f"{run_id}_input.jpg" with open(f"static/images/{input_filename}", "wb") as f: f.write(current_image_bytes) image_public_url = f"{RUBIKA_SPACE_URL}/static/images/{input_filename}" dispatch_payload = { "event_type": "generate-video", "client_payload": { "prompt": eng_prompt, "duration": 5.0, "image_url": image_public_url, "run_id": run_id, "space_url": RUBIKA_SPACE_URL } } try: resp = await asyncio.to_thread(requests.post, dispatch_url, headers=dispatch_headers, json=dispatch_payload, timeout=20) if resp.status_code != 204: await asyncio.sleep(4) continue except Exception: await asyncio.sleep(4) continue if loops_needed == 1: wait_msg = "⏳ هوش مصنوعی در حال ساخت ویدیوی شماست.\n(این فرآیند معمولاً بین ۲ تا ۴ دقیقه زمان می‌برد. لطفاً صبور باشید...)" else: wait_msg = "⏳ هوش مصنوعی در حال پردازش و رندر این بخش می‌باشد...\n(ممکن است چند دقیقه زمان ببرد)" proc_msg = await update_status_msg(client, chat_id, proc_msg, wait_msg) waited = 0 while waited < 180: # انتظار تا 180 ثانیه (3 دقیقه) await asyncio.sleep(5) waited += 5 for ext in ['mp4', 'webp', 'gif']: file_path = f"static/images/{run_id}.{ext}" if os.path.exists(file_path): await asyncio.sleep(2) try: with open(file_path, "rb") as f: new_clip_bytes = f.read() os.remove(file_path) except: pass break if new_clip_bytes: break try: os.remove(f"static/images/{input_filename}") except: pass if new_clip_bytes: break else: await asyncio.sleep(5) if not new_clip_bytes: return await update_status_msg(client, chat_id, proc_msg, "❌ متاسفانه به دلیل ترافیک سنگین سرورهای رندر، عملیات متوقف شد. لطفاً کمی بعد مجدداً تلاش کنید.") if base_video_bytes is not None: proc_msg = await update_status_msg(client, chat_id, proc_msg, "🔄 در حال متصل کردن ویدیوهای ساخته شده به یکدیگر...") base_video_bytes = await asyncio.to_thread(local_merge_videos, base_video_bytes, new_clip_bytes) else: base_video_bytes = new_clip_bytes if loop_idx < loops_needed - 1: current_secs = (loop_idx + 1) * 5 proc_msg = await update_status_msg(client, chat_id, proc_msg, f"✅ {current_secs} ثانیه از ویدیو با کیفیت بالا ساخته شد!\n📸 در حال پردازش ثانیه‌های بعدی...") current_image_bytes = await asyncio.to_thread(helper_extract_last_frame, base_video_bytes) if not current_image_bytes: await update_status_msg(client, chat_id, proc_msg, "⚠️ مشکلی در پردازش فریم‌های میانی رخ داد. عملیات در همین نقطه پایان یافت.") break proc_msg = await update_status_msg(client, chat_id, proc_msg, "🚀 ویدیو کاملاً آماده شد! در حال آپلود سریع در سرور روبیکا...") try: file_name = f"video_{uuid.uuid4().hex}.mp4" await asyncio.to_thread(sync_write_file, file_name, base_video_bytes) caption_text = f"🎞 ویدیو شما با موفقیت آماده شد!\n⏱ زمان: {duration_int} ثانیه\n✨ پرامپت اعمال شده:\n{eng_prompt}" upload_result = False for up_att in range(3): res = await helper_upload_file(client, chat_id, file_name, "Video", caption_text) if res is True: upload_result = True break await asyncio.sleep(4) if upload_result is True: if video_type == "animate" and not is_prem: user_credits_db[str_chat_id]["animate_image"] -= 1 save_db(user_credits_db) try: msg_id = getattr(proc_msg, 'message_id', None) if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id') if msg_id: await client.delete_messages(chat_id,[msg_id]) except: pass else: await update_status_msg(client, chat_id, proc_msg, "❌ ویدیو ساخته شد اما سیستم در آپلود با مشکل مواجه شد.") if os.path.exists(file_name): os.remove(file_name) except Exception as e: await update_status_msg(client, chat_id, proc_msg, f"❌ خطای آپلود:\n{str(e)[:150]}") # ============================================================================== # 🟢 پارت 20: بدنه اصلی ربات (Handler)، دستورات مدیریتی، حلقه اصلی و استارت # ============================================================================== BOT_START_TIME = time.time() async def ram_garbage_collector(): while True: await asyncio.sleep(1800) now = time.time() try: for uid in list(user_states.keys()): if now - user_states[uid].get("last_time", 0) > 3600: if user_states[uid].get("file_bytes") is not None: user_states[uid]["file_bytes"] = None if user_states[uid].get("ref_bytes") is not None: user_states[uid]["ref_bytes"] = None if len(user_states[uid].get("history",[])) > 5: user_states[uid]["history"] = user_states[uid]["history"][-5:] for uid in list(user_message_times.keys()): times = user_message_times[uid] valid_times =[t for t in times if now - t < 3.0] if not valid_times: del user_message_times[uid] else: user_message_times[uid] = valid_times except Exception: pass if not bot_token: print("خطا: توکن ربات روبیکا وارد نشده است!") else: bot = BotClient(bot_token) @bot.on_update(filters.private) async def main_handler(client, update): global BOT_GUID try: if not BOT_GUID: try: me_info = await client.get_me() if me_info and hasattr(me_info, 'user'): BOT_GUID = getattr(me_info.user, 'user_guid', None) except Exception: pass msg_obj = getattr(update, "message", None) or getattr(update, "new_message", None) author_id = getattr(update, 'author_guid', None) if not author_id and msg_obj: author_id = msg_obj.get('author_object_guid') if isinstance(msg_obj, dict) else getattr(msg_obj, 'author_object_guid', None) if BOT_GUID and author_id == BOT_GUID: return chat_id = getattr(update, 'object_guid', None) or getattr(update, 'author_guid', None) or getattr(update, "chat_id", None) if not chat_id: return msg_id = getattr(update, "message_id", None) if not msg_id and msg_obj: msg_id = msg_obj.get("message_id") if isinstance(msg_obj, dict) else getattr(msg_obj, "message_id", None) str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip() creds = get_user_credits(str_chat_id) if msg_id: str_msg_id = str(msg_id).strip() if is_message_processed(str_msg_id): return is_old = False try: for attr in['timestamp', 'time', 'date', 'message_date']: val = getattr(msg_obj, attr, None) if not val and isinstance(msg_obj, dict): val = msg_obj.get(attr) if val: val_int = int(val) if val_int > 10000000000: val_int = val_int / 1000 if val_int < BOT_START_TIME - 60: is_old = True break except Exception: pass mark_message_processed(str_msg_id) if is_old: return user_text = getattr(update, "text", "") or getattr(msg_obj, "text", "") user_text_str = str(user_text).strip() if user_text else "" user_text_lower = user_text_str.lower() if is_user_spamming(str_chat_id): return if str_chat_id not in user_states: user_states[str_chat_id] = {"mode": None, "text": "", "history":[], "file_bytes": None, "file_name": None, "last_time": time.time(), "video_type": None, "selected_model": None, "vid_width": 1024, "vid_height": 1024, "entry_point": None} else: user_states[str_chat_id]["last_time"] = time.time() if user_text_lower.startswith(f"{ADMIN_CODE} pro=") or user_text_lower.startswith(f"{ADMIN_CODE}pro="): parts = user_text_str.split("=", 1) if len(parts) >= 2: target_id = parts[1].replace("`", "").replace("'", "").replace('"', "").strip() if target_id: if target_id not in user_credits_db or not isinstance(user_credits_db[target_id], dict): user_credits_db[target_id] = { "is_premium": False, "expire_date": None, "last_reset": "", "last_weekly_reset": 0, "chat": 10, "image_flux": 3, "image_midjourney": 3, "image_cartoon": 3, "edit_image": 3, "animate_image": 3, "podcast": 2, "tts": 5, "file": 1, "stt": 5, "voice_conv": 3, "voice_clone": 1, "last_msg_id": 0, "has_joined": True, "invited_count": 0, "used_referral": False, "referral_code": "" } user_credits_db[target_id]["is_premium"] = True expire_time = datetime.datetime.now() + datetime.timedelta(days=30) user_credits_db[target_id]["expire_date"] = expire_time.isoformat() user_credits_db[target_id]["chat"] = 999999 user_credits_db[target_id]["image_flux"] = 999999 user_credits_db[target_id]["image_midjourney"] = 999999 user_credits_db[target_id]["image_cartoon"] = 999999 user_credits_db[target_id]["edit_image"] = 999999 user_credits_db[target_id]["podcast"] = 999999 user_credits_db[target_id]["tts"] = 999999 user_credits_db[target_id]["file"] = 999999 user_credits_db[target_id]["stt"] = 999999 user_credits_db[target_id]["voice_conv"] = 999999 user_credits_db[target_id]["voice_clone"] = 999999 user_credits_db[target_id]["animate_image"] = 999999 save_db(user_credits_db) await send_with_keyboard(client, chat_id, f"✅ حساب کاربر `{target_id}` به مدت ۳۰ روز شارژ شد و به پرو ارتقا یافت.", False) try: await send_with_keyboard(client, target_id, "🎉 **کاربر گرامی، تبریک!**\n\nحساب شما با موفقیت توسط پشتیبانی به **🌟 نسخه پرو (ویژه)** ارتقا یافت.\nهم‌اکنون بسته‌های نامحدود و طلایی یک‌ماهه شما فعال گردید.\n\nجهت مشاهده جزئیات روی دکمه «حساب کاربری 👤» کلیک کنید.", True) except Exception: pass return if user_text_lower.startswith(f"{ADMIN_CODE} free=") or user_text_lower.startswith(f"{ADMIN_CODE}free="): parts = user_text_str.split("=", 1) if len(parts) >= 2: target_id = parts[1].replace("`", "").replace("'", "").replace('"', "").strip() if target_id: if target_id in user_credits_db and isinstance(user_credits_db[target_id], dict): user_credits_db[target_id]["is_premium"] = False user_credits_db[target_id]["expire_date"] = None user_credits_db[target_id]["last_reset"] = "" user_credits_db[target_id]["image_flux"] = 3 user_credits_db[target_id]["image_midjourney"] = 3 user_credits_db[target_id]["image_cartoon"] = 3 user_credits_db[target_id]["edit_image"] = 3 user_credits_db[target_id]["animate_image"] = 3 save_db(user_credits_db) await send_with_keyboard(client, chat_id, f"✅ اشتراک کاربر `{target_id}` لغو شد و به رایگان تبدیل گشت.", False) try: await send_with_keyboard(client, target_id, "⚠️ کاربر گرامی، اشتراک ویژه شما به پایان رسید و حساب شما به نسخه رایگان (آزمایشی) تغییر یافت.", True) except Exception: pass return if user_text_lower.startswith(f"{ADMIN_CODE} =") or user_text_lower.startswith(f"{ADMIN_CODE}="): parts = user_text_str.split("=", 1) if len(parts) >= 2: target_id = parts[1].replace("`", "").replace("'", "").replace('"', "").strip() if target_id: if target_id in user_credits_db and isinstance(user_credits_db[target_id], dict): t_creds = user_credits_db[target_id] is_prem = t_creds.get("is_premium", False) if is_prem: expire_txt = "نامشخص" days_left = 0 if t_creds.get("expire_date"): exp_d = datetime.datetime.fromisoformat(t_creds["expire_date"]) now = datetime.datetime.now() diff = exp_d - now days_left = diff.days if diff.days >= 0 else 0 jy, jm, jd = gregorian_to_jalali(exp_d.year, exp_d.month, exp_d.day) expire_txt = f"{jy:04d}/{jm:02d}/{jd:02d}" status_text = f"🌟 نسخه پرو (ویژه)\n📅 انقضا: {expire_txt}\n⏳ باقیمانده: {days_left} روز" else: status_text = "🥉 نسخه رایگان (آزمایشی)\n⏳ وضعیت: سهمیه روزانه محدود" chat_rem = "نامحدود ∞" if is_prem else t_creds.get('chat', 0) vc_rem = "نامحدود ∞" if is_prem else t_creds.get('voice_conv', 0) clone_rem = "نامحدود ∞" if is_prem else t_creds.get('voice_clone', 0) podcast_rem = "نامحدود ∞" if is_prem else t_creds.get('podcast', 0) tts_rem = "نامحدود ∞" if is_prem else t_creds.get('tts', 0) file_rem = "نامحدود ∞" if is_prem else t_creds.get('file', 0) stt_rem = "نامحدود ∞" if is_prem else t_creds.get('stt', 0) image_flux_rem = "نامحدود ∞" if is_prem else t_creds.get('image_flux', 0) image_mj_rem = "نامحدود ∞" if is_prem else t_creds.get('image_midjourney', 0) image_car_rem = "نامحدود ∞" if is_prem else t_creds.get('image_cartoon', 0) edit_image_rem = "نامحدود ∞" if is_prem else t_creds.get('edit_image', 0) anim_rem = "نامحدود ∞" if is_prem else t_creds.get('animate_image', 0) invs = t_creds.get('invited_count', 0) joined_status = "بله ✅" if t_creds.get('has_joined') else "خیر ❌" info_msg = f"""🔍 **اطلاعات اختصاصی کاربر:** 👤 شناسه: `{target_id}` عضو کانال: {joined_status} 🔹 **وضعیت اشتراک:** {status_text} 🎁 **تعداد افراد دعوت شده:** {invs} نفر 📊 **سهمیه باقی‌مانده فعلی:** 💬 چت: {chat_rem} 🎨 تولید عکس (فلاکس): {image_flux_rem} 🎨 تولید عکس (میدجرنی): {image_mj_rem} 🎨 تولید عکس (کارتونی): {image_car_rem} 🪄 ویرایش عکس: {edit_image_rem} 🎞️ متحرک‌سازی تصاویر: {anim_rem} 🎙️ تغییر صدا: {vc_rem} 👤 کلون کردن صدا: {clone_rem} 🎙 پادکست: {podcast_rem} 🗣 متن به صدا: {tts_rem} 📁 تحلیل فایل: {file_rem} 📝 صدا به متن: {stt_rem}""" await send_with_keyboard(client, chat_id, info_msg, False) else: await send_with_keyboard(client, chat_id, f"❌ کاربری با شناسه `{target_id}` در دیتابیس ربات یافت نشد.", False) return is_file = False file_name = f"unknown_file_{uuid.uuid4().hex[:6]}.jpg" if msg_obj: for attr in['file', 'file_inline', 'photo', 'voice', 'audio', 'document', 'video']: file_attr = getattr(msg_obj, attr, None) if file_attr: is_file = True file_name = getattr(file_attr, 'file_name', file_name) break if not is_file and hasattr(msg_obj, 'to_dict'): msg_dict = msg_obj.to_dict() for attr in['file', 'file_inline', 'photo', 'voice', 'audio', 'document', 'video']: if attr in msg_dict: is_file = True file_attr = msg_dict.get(attr, {}) file_name = file_attr.get('file_name', file_name) if isinstance(file_attr, dict) else file_name break if user_text_str == "✅ عضو شدم": if not creds.get("has_joined", False): user_credits_db[str_chat_id]["has_joined"] = True save_db(user_credits_db) await send_with_keyboard(client, chat_id, "🎉 **عضویت شما با موفقیت تایید شد! خیلی خوش آمدید.**\n\nحالا می‌توانید از تمامی امکانات ربات استفاده کنید.\nلطفاً یکی از گزینه‌های منو را انتخاب کنید:", True) return else: await send_with_keyboard(client, chat_id, "✅ شما قبلاً عضو شده‌اید! لطفا از منوی اصلی استفاده کنید.", True) return if not creds.get("has_joined", False): join_msg = "👋 **سلام کاربر گرامی!**\n\nجهت استفاده از خدمات ربات هوش مصنوعی آلفا و مطلع شدن از آخرین آپدیت‌ها، لطفا ابتدا در کانال رسمی ما عضو شوید:\n\n📢 **آیدی کانال:** @aialpha\n\n👇 پس از عضویت در کانال، بر روی دکمه **«✅ عضو شدم»** کلیک کنید تا ربات برای شما فعال شود." try: payload = {"chat_id": chat_id, "text": join_msg, "chat_keypad_type": "New", "chat_keypad": JOIN_KEYPAD_DICT} await client._make_request("sendMessage", payload) except Exception: await client.send_message(chat_id, join_msg) return if user_text_lower.startswith("/start"): user_states[str_chat_id]["mode"] = None user_states[str_chat_id]["file_bytes"] = None await send_with_keyboard(client, chat_id, "سلام! به ربات هوش مصنوعی آلفا خوش آمدید 🤖\n\nلطفاً برای شروع، از کیبورد پایین یکی از بخش‌ها را انتخاب کنید:", True) return if user_text_str in["سلام", "لغو", "/cancel", "❌ لغو", "برگشت♻️"]: user_states[str_chat_id]["mode"] = None user_states[str_chat_id]["file_bytes"] = None await send_with_keyboard(client, chat_id, "سلام! به ربات هوش مصنوعی آلفا خوش آمدید 🤖\n\nلطفاً برای شروع، از کیبورد پایین یکی از بخش‌ها را انتخاب کنید:", True) return if user_text_str in["/account", "حساب کاربری 👤"]: is_prem = creds.get("is_premium", False) if is_prem: expire_txt = "نامشخص" days_left = 0 if creds.get("expire_date"): exp_d = datetime.datetime.fromisoformat(creds["expire_date"]) now = datetime.datetime.now() diff = exp_d - now days_left = diff.days if diff.days >= 0 else 0 jy, jm, jd = gregorian_to_jalali(exp_d.year, exp_d.month, exp_d.day) expire_txt = f"{jy:04d}/{jm:02d}/{jd:02d} (ساعت {exp_d.hour:02d}:{exp_d.minute:02d})" status_text = "🌟 نسخه پرو (ویژه)" expire_info = f"\n📅 **تاریخ انقضا:** {expire_txt}\n⏳ **زمان باقیمانده:** {days_left} روز" daily_note = "*نکته: سهمیه پردازشی شما مختص همین دوره یک‌ماهه می‌باشد.*" else: status_text = "🥉 نسخه رایگان (آزمایشی)" expire_info = "" daily_note = "*نکته: سهمیه شما هر روز ساعت ۰۰:۰۰ بامداد به صورت خودکار مجدداً شارژ می‌گردد.*" chat_rem = "نامحدود ∞" if is_prem else creds['chat'] vc_rem = "نامحدود ∞" if is_prem else creds.get('voice_conv', 0) clone_rem = "نامحدود ∞" if is_prem else creds.get('voice_clone', 0) podcast_rem = "نامحدود ∞" if is_prem else creds['podcast'] tts_rem = "نامحدود ∞" if is_prem else creds['tts'] file_rem = "نامحدود ∞" if is_prem else creds['file'] stt_rem = "نامحدود ∞" if is_prem else creds['stt'] image_flux_rem = "نامحدود ∞" if is_prem else creds.get('image_flux', 0) image_mj_rem = "نامحدود ∞" if is_prem else creds.get('image_midjourney', 0) image_car_rem = "نامحدود ∞" if is_prem else creds.get('image_cartoon', 0) edit_image_rem = "نامحدود ∞" if is_prem else creds.get('edit_image', 0) anim_rem = "نامحدود ∞" if is_prem else creds.get('animate_image', 0) invited_count = creds.get('invited_count', 0) my_code = get_or_create_referral_code(str_chat_id) account_profile = f"""👤 **اطلاعات حساب کاربری شما** 🔹 **شناسه یکتا:** `{chat_id}` 🎫 **کد هدیه شما:** `{my_code}` 🔹 **وضعیت اشتراک:** {status_text}{expire_info} 🎁 **تعداد افراد دعوت شده:** {invited_count} نفر 📊 **سهمیه باقی‌مانده شما:** - 💬 چت هوشمند: {chat_rem} - 🎨 تولید عکس (فلاکس): {image_flux_rem} عدد - 🎨 تولید عکس (میدجرنی): {image_mj_rem} عدد - 🎨 تولید عکس (کارتونی): {image_car_rem} عدد - 🪄 ویرایش تصویر: {edit_image_rem} عدد - 🎞️ متحرک‌سازی تصاویر (هفتگی): {anim_rem} عدد - 🎙️ تغییر صدا: {vc_rem} - 👤 کلون کردن صدا: {clone_rem} - 🎙 ساخت پادکست: {podcast_rem} - 🗣 تبدیل متن به صدا: {tts_rem} - 📁 تحلیل فایل و سند: {file_rem} - 📝 تبدیل صدا به متن: {stt_rem} {daily_note}""" await send_with_keyboard(client, chat_id, account_profile, True) return if user_text_str in["/invite", "دعوت دوستان 🎁"]: invites = creds.get("invited_count", 0) remains = 10 - (invites % 10) my_code = get_or_create_referral_code(str_chat_id) invite_text = f"""🎁 **سیستم دعوت دوستان (دو سر سود)** با دعوت دوستان خود به ربات آلفا، هم شما و هم دوستتان هدیه می‌گیرید! به دوست خود بگویید پس از ورود به ربات، دکمه **«ثبت کد هدیه 🎫»** را بزند و کد زیر را وارد کند. ✨ **سود دوست شما:** در همان لحظه 10 تبدیل رایگان متن به صدا دریافت می‌کند. ✨ **سود شما:** به آمار دعوت‌هایتان اضافه می‌شود و به ازای هر **10 نفر**، **3 روز اشتراک پرو نامحدود** می‌گیرید. 📊 **آمار شما:** - تعداد دعوت‌های موفق: {invites} نفر - دعوت‌های باقی‌مانده تا جایزه بعدی: {remains} نفر کد هدیه اختصاصی شما: `{my_code}` (متن زیر را کپی کرده و برای دوستانتان بفرستید 👇)""" await send_with_keyboard(client, chat_id, invite_text, True) forward_text = f"""🤖 **ربات هوش مصنوعی آلفا پرو** ✨ با این ربات می‌تونی کارهای زیر رو به راحتی انجام بدی: 💬 چت با پیشرفته‌ترین هوش مصنوعی 🎨 ساخت و ویرایش حرفه‌ای عکس 🎙️ تغییر صدا و کلون کردن صدا 🎙 ساخت پادکست اختصاصی 🗣 تبدیل متن به صدا (30 گوینده مختلف) 📝 تبدیل صدا و ویدیو به متن 📁 تحلیل فایل‌ها و ساخت مقاله 👇 اول وارد ربات زیر شو: @aialphabot سپس دکمه **«ثبت کد هدیه 🎫»** را بزن و کد 8 رقمی زیر رو وارد کن تا همون اول **10 تا تبدیل صدا هدیه بگیری**: `{my_code}`""" await send_with_keyboard(client, chat_id, forward_text, False) return if user_text_str in["/referral", "ثبت کد هدیه 🎫"]: if creds.get("used_referral", False): await send_with_keyboard(client, chat_id, "❌ شما قبلاً کد هدیه یک نفر را ثبت کرده‌اید و فقط یک‌بار مجاز به استفاده از این امکان هستید.", True) return user_states[str_chat_id]["mode"] = "waiting_for_referral_code" msg = "🎫 **ثبت کد هدیه**\n\nکد هدیه 8 رقمی (اعداد) که از دوست خود دریافت کرده‌اید را اینجا وارد کنید تا در همان لحظه **10 سهمیه تبدیل رایگان متن به صدا** هدیه بگیرید!\n\n(برای انصراف دکمه «برگشت♻️» را بزنید)" await send_with_keyboard(client, chat_id, msg, True) return if user_text_str in["/buy", "خرید اشتراک 💎"]: buy_text = f"""💎 **خرید اشتراک ویژه آلفا پرو (یک ماهه)** با تهیه اشتراک ویژه، محدودیت‌ها را کنار بزنید و از نهایت قدرت هوش مصنوعی لذت ببرید! 🚀 ━━━━━━━━━━━━━━━━━━━ 🎁 **بسته طلایی یک‌ماهه شامل:** 🤖 چت با هوش مصنوعی: نامحدود ∞ 🎙️ تغییر صدا و کلون کردن صدا: نامحدود ∞ 🗣 تبدیل متن به صدا (۳۰ گوینده): نامحدود ∞ 🎙 ساخت پادکست: نامحدود ∞ 📁 تحلیل فایل و سند: نامحدود ∞ 📝 تبدیل فایل صوتی به متن: نامحدود ∞ 🪄 ویرایش تصویر: نامحدود ∞ 🎨 تولید تصویر (۳ مدل قدرتمند): نامحدود ∞ 🎞️ متحرک‌سازی و ساخت ویدیو: نامحدود ∞ ━━━━━━━━━━━━━━━━━━━ 💳 **هزینه اشتراک یک ماهه:** 250 هزار تومان 💳 **شماره کارت جهت واریز:** ➖➖➖➖➖➖➖➖ `6219861411958035` ➖➖➖➖➖➖➖➖ 👤 **به نام:** کوهی ✅ **نحوه فعال‌سازی:** پس از واریز مبلغ، لطفاً رسید پرداختی را به همراه **شناسه یکتای خود** (که در پایین آمده) به آیدی پشتیبانی زیر ارسال کنید تا اشتراک شما فعال گردد: 🔑 **شناسه یکتای شما:** `{chat_id}` 👨‍💻 **ارتباط با پشتیبانی:** 🆔 @aialpha_admin""" await send_with_keyboard(client, chat_id, buy_text, True) return if user_text_str in["/transfer", "انتقال اکانت از برنامه به ربات"]: transfer_text = f"""🔄 **انتقال اکانت از برنامه به ربات** کاربر گرامی، در صورتی که داخل برنامه «هوش مصنوعی آلفا» پیش‌تر اشتراک تهیه کرده‌اید، نیازی به خرید مجدد اشتراک داخل ربات نیست! 🎉 کافیست **شناسه یکتای** ربات روبیکای خود را کپی کرده و برای پشتیبانی ما در برنامه هوش مصنوعی آلفا ارسال کنید تا اکانت اشتراکی شما به سرعت از برنامه به ربات روبیکا انتقال داده شود. 🔑 **شناسه یکتای ربات شما:** `{chat_id}` 👨‍💻 **دقت کنید شناسه ربات رو به پشتیبانی داخل خود برنامه هوش مصنوعی آلفا ارسال کنید.**""" await send_with_keyboard(client, chat_id, transfer_text, True) return if user_text_str in ["ساخت ویدیو با تصویر 🖼️", "ساخت ویدیو با متن 📝"]: if not creds.get("is_premium", False): msg = "💎 ساخت ویدیو در نسخه رایگان در دسترس نیست. برای ساخت ویدیو بصورت نامحدود و استفاده از کل بخش های برنامه بصورت نامحدود اشتراک تهیه کنید." return await send_with_keyboard(client, chat_id, msg, False) if "تصویر" in user_text_str: user_states[str_chat_id]["mode"] = "vid_img_wait_file" user_states[str_chat_id]["video_type"] = "premium_img" user_states[str_chat_id]["selected_model"] = None await send_with_keyboard(client, chat_id, "🖼️ شما وارد بخش **ساخت ویدیو از تصویر** شدید.\n\nلطفاً تصویر خود را ارسال کنید:\n(برای خروج دکمه «برگشت♻️» را بزنید)", True) else: user_states[str_chat_id]["mode"] = "vid_txt_wait_prompt" user_states[str_chat_id]["video_type"] = "premium_txt" user_states[str_chat_id]["selected_model"] = None await send_with_keyboard(client, chat_id, "📝 شما وارد بخش **ساخت ویدیو از متن** شدید.\n\nلطفاً پرامپت و متنی که می‌خواهید به ویدیو تبدیل شود را با جزئیات بنویسید:\n(برای خروج دکمه «برگشت♻️» را بزنید)", True) return if user_text_str == "متحرک سازی تصاویر 🎞️": if not creds.get("is_premium", False) and creds.get("animate_image", 0) <= 0: msg = "⚠️ **اعتبار متحرک‌سازی تصاویر شما در این هفته به پایان رسیده است!**\nکاربران رایگان در هر هفته ۳ سهمیه ویدیو ۵ ثانیه‌ای دارند. برای دسترسی نامحدود اشتراک تهیه کنید." return await send_with_keyboard(client, chat_id, msg, False) user_states[str_chat_id]["mode"] = "anim_wait_file" user_states[str_chat_id]["video_type"] = "animate" user_states[str_chat_id]["selected_model"] = None await send_with_keyboard(client, chat_id, "🎞️ شما وارد بخش **متحرک‌سازی تصاویر** شدید.\n\nلطفاً تصویری که قصد متحرک کردن آن را دارید بفرستید:\n(برای خروج دکمه «برگشت♻️» را بزنید)", True) return if user_text_str in["/chat", "💬 چت", "چت با هوش مصنوعی 🤖"]: user_states[str_chat_id]["mode"] = "chat" user_states[str_chat_id]["history"] = [] user_states[str_chat_id]["entry_point"] = "chat" await send_with_keyboard(client, chat_id, "💬 شما وارد بخش **چت با هوش مصنوعی** شدید.\n\nهر سوالی دارید بفرستید تا جواب بدم:\n(برای خروج دکمه «برگشت♻️» را بزنید)", True) return if user_text_str in["/image_analysis", "تحلیل تصویر 🔍"]: user_states[str_chat_id]["mode"] = "chat" user_states[str_chat_id]["history"] = [] user_states[str_chat_id]["entry_point"] = "image_analysis" await send_with_keyboard(client, chat_id, "🖼️ شما وارد بخش **تحلیل تصویر اختصاصی** شدید.\n\nلطفاً تصویر خود را ارسال کنید:\n(برای خروج دکمه «برگشت♻️» را بزنید)", True) return if user_text_str in["/image", "🎨 عکس", "ساخت تصاویر🎨"]: user_states[str_chat_id]["mode"] = "image_waiting_for_text" await send_with_keyboard(client, chat_id, "🎨 شما وارد بخش **ساخت عکس پیشرفته** شدید.\n\nمتن (ایده) خود را به صورت کامل ارسال کنید:\n(برای خروج دکمه «برگشت♻️» را بزنید)", True) return if user_text_str in["/edit_image", "ویرایش تصاویر 🪄"]: user_states[str_chat_id]["mode"] = "image_edit_waiting_for_image" user_states[str_chat_id]["file_bytes"] = None await send_with_keyboard(client, chat_id, "🪄 به بخش **ویرایش عکس پیشرفته** خوش آمدید.\n\nلطفاً ابتدا عکسی که می‌خواهید ویرایش کنید را بفرستید:\n(برای خروج دکمه «برگشت♻️» را بزنید)", True) return if user_text_str in ["/tts", "🎙️ صدا", "تبدیل متن به صدا🗣️"]: user_states[str_chat_id]["mode"] = "tts_waiting_for_text" tts_msg = """🎙️ شما وارد بخش **تبدیل متن به صدا** شدید. 📝 لطفاً متنی که می‌خواهید به صدا تبدیل شود را ارسال کنید. 🌍 **پشتیبانی از تمامی زبان‌های دنیا!** 🎭 **درک لحن و احساسات:** هوش مصنوعی ما احساسات رو درک می‌کنه! کافیه لحن دلخواهت رو داخل پرانتز بنویسی. 💡 *مثال:* هی، گوش کن! یه راز دارم که فقط به تو می‌گم. (با لحنی مرموز، شیطنت‌آمیز و نجواگونه) (برای خروج دکمه «برگشت♻️» را بزنید)""" await send_with_keyboard(client, chat_id, tts_msg, True) return if user_text_str in ["/podcast", "📻 پادکست", "ساخت پادکست 🎙️"]: user_states[str_chat_id]["mode"] = "podcast_waiting_for_topic" podcast_msg = """📻 شما وارد بخش **ساخت پادکست** شدید. لطفاً موضوع پادکست خود را بفرستید. این بخش متصل به هوش مصنوعی زبانی است و درخواست شما را قبل از ساخت کاملاً درک می‌کند! 🧠 ➖➖➖➖➖➖➖➖➖➖ 💡 **راهنمای استفاده:** 🔹 می‌توانید فقط یک موضوع بدهید (مثال: درباره تاریخچه پیدایش قهوه با سه گوینده یک پادکست جذاب بساز). 🔹 می‌توانید متن کامل یک مقاله از یک سایت را بفرستید تا هوش مصنوعی خودش محتوای اصلی را استخراج کرده و پادکست بسازد. 🔹 امکان مشخص کردن تعداد گویندگان و اسم آن‌ها به انتخاب شما وجود دارد! ➖➖➖➖➖➖➖➖➖➖ 🎙️ **لیست ۳۰ گوینده اختصاصی ما:** شهاب | آوا | نوید | آرمان | مهسا | دانا | سامان | آرش | شبنم | سحر | مریم | بهرام | نیکان | فرناز | سارا | مانی | آرتین | دلنواز | روژان | امید | بردیا | ترانه | نیکو | هستی | کامیار | کیانوش | پویا | مهتاب | سام | لیدا ➖➖➖➖➖➖➖➖➖➖ 📌 **مثال برای درخواست:** یک پادکست برام بساز در باره مدیریت زمان با دو گوینده امید و مهسا اگر گوینده و تعداد آن از سمت شما مشخص نشه هوش مصنوعی خودش انتخاب میکنه (برای خروج دکمه «برگشت♻️» را بزنید)""" await send_with_keyboard(client, chat_id, podcast_msg, True) return if user_text_str in["/vc", "تغییر صدا 🔊"]: user_states[str_chat_id]["mode"] = "vc_waiting_for_voice" user_states[str_chat_id]["file_bytes"] = None await send_with_keyboard(client, chat_id, "🎙️ شما وارد بخش **تغییر صدا** شدید.\n\nلطفاً صدای خود (ویس) را بفرستید تا آن را به صدای شخصیت‌های معروف تبدیل کنم:\n(برای خروج دکمه «برگشت♻️» را بزنید)", True) return if user_text_str in["/clone", "کلون کردن صدا 👤"]: user_states[str_chat_id]["mode"] = "clone_waiting_for_src" user_states[str_chat_id]["file_bytes"] = None user_states[str_chat_id]["ref_bytes"] = None clone_text = """👤 **شما وارد بخش کلون کردن صدای اختصاصی شدید. با یک فایل صوتی کوتاه از هر شخصی صدای خودتونو به همون صدا تغییر بدید.** ✨ **این بخش چه کاری انجام می‌دهد؟** با این ویژگی بی‌نظیر، شما می‌توانید صدای خود (یا هر فرد دیگری) را دقیقاً شبیه‌سازی کنید! کافیست یک نمونه از صدای خودتان صحبت کنید و یک نمونه از صدای شخص مورد نظر (الگو) را به هوش مصنوعی بدهید. ربات لحن و کلمات شما را گرفته و دقیقاً با جنس صدای شخص الگو بازسازی می‌کند. 📌 **راهنمای استفاده:** شما در این مرحله به **2 فایل صوتی** نیاز دارید: 1️⃣ **صدای ورودی (شما):** صدایی که می‌خواهید متن و لحن آن خوانده شود. 2️⃣ **صدای الگو (هدف):** صدایی که می‌خواهید خروجی نهایی شبیه به آن شود (بدون نویز و موزیک پس‌زمینه، بین ۳ تا ۱۰ ثانیه بهترین نتیجه را می‌دهد). 👇 **مرحله اول:** لطفاً ابتدا **صدای خودتان (ورودی)** را به صورت ویس یا فایل صوتی ارسال کنید: *(برای خروج دکمه «برگشت♻️» را بزنید)*""" await send_with_keyboard(client, chat_id, clone_text, True) return if user_text_str in["/stt", "فایل صوتی به متن 📝"]: user_states[str_chat_id]["mode"] = "stt_waiting_for_audio" await send_with_keyboard(client, chat_id, "📝 شما وارد بخش **تبدیل صدا به متن** شدید.\n\nلطفاً فایل خود (ویس، آهنگ، ویدیو و...) را ارسال کنید:\n(برای خروج دکمه «برگشت♻️» را بزنید)", True) return if user_text_str in["/create_file", "ساخت فایل 📄"]: user_states[str_chat_id]["mode"] = "create_file_waiting_for_topic" await send_with_keyboard(client, chat_id, "📄 شما وارد بخش **ساخت فایل** شدید.\n\nلطفاً موضوع مقاله‌ای که می‌خواهید را کامل بفرستید.\nمثال: نحوه مدیریت زمان\n\nهوش مصنوعی یک مقاله کامل و طولانی نوشته و در نهایت فایل PDF و Word آن را به شما تحویل می‌دهد.\n\n(برای خروج دکمه «برگشت♻️» را بزنید)", True) return current_mode = user_states[str_chat_id].get("mode") # --- نگهبان هوشمند کلمات ممنوعه --- restricted_modes = ["image_waiting_for_text", "image_edit_waiting_for_prompt", "vid_txt_wait_prompt", "vid_wait_prompt"] if current_mode in restricted_modes and user_text_str: forbidden_words = { "sex", "porn", "nude", "erotic", "سکس", "پورن", "شهوانی", "برهنه", "لخت", "فاحشه", "پدوفیل", "گاییدن", "لزبین", "کیر", "کوس", "تجاوز", "حشری", "خودارضایی" } if any(word in user_text_str.lower() for word in forbidden_words): await send_with_keyboard(client, chat_id, "❌ **خطا:** متن ورودی شما شامل کلمات نامناسب است. لطفاً متن خود را تغییر دهید.", False) return # -------------------------------- if current_mode is None: if is_file: pass elif user_text_str: await send_with_keyboard(client, chat_id, "⚠️ لطفاً ابتدا از کیبورد پایین، بخش مورد نظرتان را انتخاب کنید:", True) return elif current_mode in ["vid_img_wait_file", "anim_wait_file"]: if not is_file: return await send_with_keyboard(client, chat_id, "⚠️ لطفاً یک تصویر (عکس) بفرستید.", False) await send_with_keyboard(client, chat_id, "📥 در حال دانلود تصویر...", False) try: user_states[str_chat_id]["file_bytes"] = await helper_download_file(client, msg_obj) user_states[str_chat_id]["mode"] = "vid_wait_prompt" v_type = user_states[str_chat_id].get("video_type") if v_type == "animate": await send_with_keyboard(client, chat_id, "✅ تصویر با موفقیت دریافت شد.\n\nاکنون توصیف کنید که می‌خواهید این تصویر چگونه متحرک شود؟ (یا یک توضیح از اتفاقات داخل آن بنویسید)", False) else: await send_with_keyboard(client, chat_id, "✅ تصویر با موفقیت دریافت شد.\n\nاکنون توصیف کنید که ویدیوی شما چگونه ساخته شود؟ (یک توضیح از اتفاقات داخل آن بنویسید)", False) except Exception as dl_err: await send_with_keyboard(client, chat_id, f"❌ خطا در دریافت تصویر.\n{dl_err}", False) return elif current_mode == "vid_txt_wait_prompt": if user_text_str: user_states[str_chat_id]["text"] = user_text_str user_states[str_chat_id]["mode"] = "vid_txt_wait_model" msg = "🖼 لطفاً انتخاب کنید تصویر ابتدایی ویدیوی شما توسط کدام مدل ساخته شود؟\n\n1️⃣ فلاکس پرو (طبیعی)\n2️⃣ میدجرنی (هنری و جذاب)\n3️⃣ کارتونی (انیمیشن)\n\n(فقط عدد 1 تا 3 را ارسال کنید)" await send_with_keyboard(client, chat_id, msg, False) else: await send_with_keyboard(client, chat_id, "⚠️ لطفاً دستور متنی را بنویسید.", False) return elif current_mode == "vid_txt_wait_model": choice = to_english_digits(user_text_str).strip() if choice in ["1", "2", "3"]: action_map = {"1": "generate-flux", "2": "generate-midjourney", "3": "generate-cartoon"} user_states[str_chat_id]["selected_model"] = action_map[choice] user_states[str_chat_id]["mode"] = "vid_txt_wait_size" msg = "📐 **لطفاً ابعاد ویدیو خود را انتخاب کنید:**\n\n1️⃣ استوری (9:16) 📱\n2️⃣ یوتیوب (16:9) 🖥️\n\n(لطفاً فقط عدد 1 یا 2 را بفرستید)" await send_with_keyboard(client, chat_id, msg, False) else: await send_with_keyboard(client, chat_id, "❌ لطفاً فقط عدد 1، 2 یا 3 را بفرستید.", False) return elif current_mode == "vid_txt_wait_size": choice = to_english_digits(user_text_str).strip() if choice in ["1", "2"]: w, h = (768, 1344) if choice == "1" else (1344, 768) user_states[str_chat_id]["vid_width"] = w user_states[str_chat_id]["vid_height"] = h user_states[str_chat_id]["mode"] = "vid_wait_duration" dur_menu = "⏱ **لطفاً زمان ویدیو را انتخاب کنید:**\n\n1️⃣ 5 ثانیه (زمان ساخت: ۲ الی ۳ دقیقه)\n2️⃣ 10 ثانیه (زمان ساخت: ۵ الی ۶ دقیقه)\n3️⃣ 15 ثانیه (زمان ساخت: ۸ الی ۹ دقیقه)\n4️⃣ 20 ثانیه (زمان ساخت: ۱۱ الی ۱۲ دقیقه)\n\n⚠️ نکته: در صورت انتخاب گزینه‌های بعدی، هر 5 ثانیه، ۳ دقیقه به زمان ساخت اضافه میشود و ممکنه طولانی بشه." await send_with_keyboard(client, chat_id, dur_menu, False) else: await send_with_keyboard(client, chat_id, "❌ لطفاً فقط عدد 1 یا 2 را بفرستید.", False) return elif current_mode == "vid_wait_prompt": if user_text_str: user_states[str_chat_id]["text"] = user_text_str user_states[str_chat_id]["mode"] = "vid_wait_duration" dur_menu = "⏱ **لطفاً زمان ویدیو را انتخاب کنید:**\n\n1️⃣ 5 ثانیه (زمان ساخت: ۲ الی ۳ دقیقه)\n2️⃣ 10 ثانیه (زمان ساخت: ۵ الی ۶ دقیقه)\n3️⃣ 15 ثانیه (زمان ساخت: ۸ الی ۹ دقیقه)\n4️⃣ 20 ثانیه (زمان ساخت: ۱۱ الی ۱۲ دقیقه)\n\n⚠️ نکته: در صورت انتخاب گزینه‌های بعدی، هر 5 ثانیه، ۳ دقیقه به زمان ساخت اضافه میشود و ممکنه طولانی بشه." await send_with_keyboard(client, chat_id, dur_menu, False) else: v_type = user_states[str_chat_id].get("video_type") if v_type == "animate": await send_with_keyboard(client, chat_id, "⚠️ لطفاً دستور متحرک‌سازی را بنویسید.", False) else: await send_with_keyboard(client, chat_id, "⚠️ لطفاً دستور ساخت ویدیو را بنویسید.", False) return elif current_mode == "vid_wait_duration": choice = to_english_digits(user_text_str).strip() if choice in ["1", "2", "3", "4"]: dur_map = {"1": 5, "2": 10, "3": 15, "4": 20} selected_dur = dur_map[choice] v_type = user_states[str_chat_id].get("video_type") is_prem = creds.get("is_premium", False) if v_type == "animate" and not is_prem: if selected_dur > 5: return await send_with_keyboard(client, chat_id, "❌ **کاربران رایگان در بخش متحرک‌سازی تصاویر تنها قادر به ساخت ویدیوهای ۵ ثانیه‌ای هستند.**\nبرای ساخت ویدیوهای طولانی‌تر، از منوی اصلی اشتراک تهیه نمایید.", False) img_bytes = user_states[str_chat_id].get("file_bytes") prompt_txt = user_states[str_chat_id].get("text") txt_model = user_states[str_chat_id].get("selected_model") w = user_states[str_chat_id].get("vid_width", 1024) h = user_states[str_chat_id].get("vid_height", 1024) user_states[str_chat_id]["mode"] = None asyncio.create_task(process_video_generation(client, chat_id, v_type, prompt_txt, selected_dur, img_bytes, txt_model, w, h)) else: await send_with_keyboard(client, chat_id, "❌ لطفاً فقط عدد 1 تا 4 را بفرستید.", False) return elif current_mode == "waiting_for_referral_code": if user_text_str: normalized_code = to_english_digits(user_text_str).strip() if not normalized_code.isdigit() or len(normalized_code) != 8: await send_with_keyboard(client, chat_id, "❌ کد وارد شده نامعتبر است. لطفاً یک کد 8 رقمی صحیح بفرستید.", False) return inviter_id = find_user_by_referral_code(normalized_code) if not inviter_id: await send_with_keyboard(client, chat_id, "❌ کد هدیه یافت نشد. لطفاً کد را با دقت بررسی کنید.", False) return if str(inviter_id) == str(str_chat_id): await send_with_keyboard(client, chat_id, "❌ شما نمی‌توانید کد خودتان را ثبت کنید!", False) return user_states[str_chat_id]["mode"] = None user_credits_db[str_chat_id]["used_referral"] = True user_credits_db[str_chat_id]["tts"] = user_credits_db[str_chat_id].get("tts", 0) + 10 user_credits_db[inviter_id]["invited_count"] = user_credits_db[inviter_id].get("invited_count", 0) + 1 current_invites = user_credits_db[inviter_id]["invited_count"] if current_invites > 0 and current_invites % 10 == 0: inviter_data = user_credits_db[inviter_id] inviter_data["is_premium"] = True now = datetime.datetime.now() if inviter_data.get("expire_date"): try: current_exp = datetime.datetime.fromisoformat(inviter_data["expire_date"]) if current_exp > now: new_exp = current_exp + datetime.timedelta(days=3) else: new_exp = now + datetime.timedelta(days=3) except: new_exp = now + datetime.timedelta(days=3) else: new_exp = now + datetime.timedelta(days=3) inviter_data["expire_date"] = new_exp.isoformat() inviter_data["chat"] = 999999 inviter_data["image_flux"] = 999999 inviter_data["image_midjourney"] = 999999 inviter_data["image_cartoon"] = 999999 inviter_data["edit_image"] = 999999 inviter_data["podcast"] = 999999 inviter_data["tts"] = 999999 inviter_data["file"] = 999999 inviter_data["stt"] = 999999 save_db(user_credits_db) try: msg_text = f"🎉 **تبریک ویژه!**\nیک دوست کد هدیه شما را ثبت کرد.\nتعداد کل دعوت‌های شما: {current_invites} نفر\n\n🎁 **هدیه شما:** ۳ روز اشتراک پرو نامحدود به حساب شما افزوده شد!" asyncio.create_task(send_with_keyboard(client, inviter_id, msg_text, True)) except: pass else: save_db(user_credits_db) remains = 10 - (current_invites % 10) try: msg_text = f"🎉 **تبریک!**\nیک دوست کد هدیه شما را ثبت کرد.\nتعداد کل دعوت‌های شما: {current_invites} نفر\n\n⏳ با دعوت {remains} نفر دیگر، ۳ روز اشتراک رایگان دریافت می‌کنید!" asyncio.create_task(send_with_keyboard(client, inviter_id, msg_text, True)) except: pass await send_with_keyboard(client, chat_id, "✅ کد هدیه با موفقیت ثبت شد!\n🎁 **10 سهمیه تبدیل رایگان متن به صدا** به حساب شما اضافه گردید.", True) return elif current_mode == "chat": if is_file: dl_msg = "📥 در حال دریافت تصویر..." if user_states[str_chat_id].get("entry_point") == "image_analysis" else "📥 در حال دانلود فایل..." await send_with_keyboard(client, chat_id, dl_msg, False) try: file_bytes = await helper_download_file(client, msg_obj) user_states[str_chat_id]["file_bytes"] = file_bytes user_states[str_chat_id]["file_name"] = file_name user_states[str_chat_id]["mode"] = "chat_waiting_for_prompt" if user_states[str_chat_id].get("entry_point") == "image_analysis": msg_text = "✅ تصویر با موفقیت دریافت شد.\n\nحالا لطفاً متنی بگویید **چگونه تحلیل شود؟**\n(می‌توانید سوال خاصی بپرسید یا فقط بخواهید تصویر را توصیف کند)" else: msg_text = "✅ فایل با موفقیت دریافت شد.\n\nحالا لطفاً متنی بفرستید و بگویید **چه کاری با این فایل انجام دهم؟**\n(مثلاً: این تصویر را توصیف کن، یا متن این سند را خلاصه کن)" await send_with_keyboard(client, chat_id, msg_text, False) except Exception as dl_err: await send_with_keyboard(client, chat_id, f"❌ خطا در دریافت فایل!\n{str(dl_err)}", False) elif user_text_str: asyncio.create_task(process_gemini(client, chat_id, user_text_str)) return elif current_mode == "chat_waiting_for_prompt": if user_text_str: saved_bytes = user_states[str_chat_id].get("file_bytes") saved_name = user_states[str_chat_id].get("file_name", "file.jpeg") user_states[str_chat_id]["mode"] = "chat" user_states[str_chat_id]["file_bytes"] = None asyncio.create_task(process_gemini(client, chat_id, user_text_str, file_bytes=saved_bytes, file_name=saved_name)) else: await send_with_keyboard(client, chat_id, "⚠️ لطفاً درخواست خود را متنی بنویسید.", False) return elif current_mode == "image_waiting_for_text": if user_text_str: user_states[str_chat_id]["text"] = user_text_str user_states[str_chat_id]["mode"] = "image_waiting_for_model" msg = """🎨 به بخش ساخت تصاویر حرفه‌ای خوش آمدید! چندین مدل ساخت تصاویر وجود داره لطفاً مدل هوش مصنوعی مورد نظر خود را انتخاب کنید: 1️⃣ **مدل فلاکس پرو** (طبیعی کیفیت بالا) 2️⃣ **مدل میدجرنی** (سبک هنری و واقع‌گرایانه فوق العاده با کیفیت) 3️⃣ **انیمیشنی و کارتونی** ( فوق‌العاده با کیفیت) (فقط عدد 1، 2 یا 3 را ارسال کنید) (برای خروج دکمه «برگشت♻️» را بزنید)""" await send_with_keyboard(client, chat_id, msg, False) else: await send_with_keyboard(client, chat_id, "⚠️ لطفاً ایده عکس خود را به صورت متنی بفرستید.", False) return elif current_mode == "image_waiting_for_model": normalized_choice = to_english_digits(user_text_str).strip() if normalized_choice in ["1", "2", "3"]: user_states[str_chat_id]["selected_model"] = normalized_choice user_states[str_chat_id]["mode"] = "image_waiting_for_size" size_menu = """🖼 **لطفاً ابعاد تصویر خود را انتخاب کنید:** 1️⃣ `1:1` (مربع) - مناسب پروفایل ⬛ 2️⃣ `9:16` (عمودی) - مناسب استوری 📱 3️⃣ `16:9` (افقی) - مناسب دسکتاپ 🖥️ 4️⃣ `4:3` (استاندارد) - مناسب چاپ 📸 (لطفاً فقط عدد 1 تا 4 را بفرستید)""" await send_with_keyboard(client, chat_id, size_menu, False) else: await send_with_keyboard(client, chat_id, "❌ عدد وارد شده نامعتبر است! لطفاً فقط یکی از اعداد 1، 2 یا 3 را بفرستید.", False) return elif current_mode == "image_waiting_for_size": normalized_choice = to_english_digits(user_text_str).strip() if normalized_choice in ["1", "2", "3", "4"]: size_c = normalized_choice model_c = user_states[str_chat_id].get("selected_model", "1") saved_text = user_states[str_chat_id].get("text", "") user_states[str_chat_id]["mode"] = None asyncio.create_task(process_image(client, chat_id, saved_text, model_c, size_c)) else: await send_with_keyboard(client, chat_id, "❌ عدد وارد شده نامعتبر است! لطفاً فقط یکی از اعداد 1 تا 4 را بفرستید.", False) return elif current_mode == "image_edit_waiting_for_image": if not is_file: return await send_with_keyboard(client, chat_id, "⚠️ لطفاً ابتدا یک عکس ارسال کنید.", False) await send_with_keyboard(client, chat_id, "📥 در حال دانلود عکس...", False) try: file_bytes = await helper_download_file(client, msg_obj) user_states[str_chat_id]["file_bytes"] = file_bytes user_states[str_chat_id]["mode"] = "image_edit_waiting_for_prompt" await send_with_keyboard(client, chat_id, "✅ عکس با موفقیت دریافت شد.\n\nحالا دستور خود را به صورت متنی تایپ کنید.\nمثال: پس زمینه عکس را حذف کن و یک ساحل قرار بده.", False) except Exception as dl_err: await send_with_keyboard(client, chat_id, f"❌ خطا در دریافت عکس!\n{str(dl_err)}", False) return elif current_mode == "image_edit_waiting_for_prompt": if user_text_str: saved_bytes = user_states[str_chat_id].get("file_bytes") user_states[str_chat_id]["mode"] = None user_states[str_chat_id]["file_bytes"] = None asyncio.create_task(process_image_edit(client, chat_id, saved_bytes, user_text_str)) else: await send_with_keyboard(client, chat_id, "⚠️ لطفاً درخواست ویرایش خود را متنی بنویسید.", False) return elif current_mode == "tts_waiting_for_text": if user_text_str: if len(user_text_str) > 2500: return await send_with_keyboard(client, chat_id, "⚠️ لطفاً متنی کوتاه‌تر از 2500 کاراکتر بفرستید.", False) user_states[str_chat_id]["text"] = user_text_str user_states[str_chat_id]["mode"] = "tts_waiting_for_speaker" speakers_menu = """✅ متن شما ذخیره شد. لطفاً **شماره** گوینده مورد نظر خود را بفرستید: 1. شهاب | 2. آوا | 3. نوید 4. آرمان | 5. مهسا | 6. دانا 7. سامان | 8. آرش | 9. شبنم 10. سحر | 11. مریم | 12. بهرام 13. نیکان| 14. فرناز | 15. سارا 16. مانی | 17. آرتین | 18. دلنواز 19. روژان | 20. امید | 21. بردیا 22. ترانه | 23. نیکو | 24. هستی 25. کامیار| 26. کیانوش| 27. پویا 28. مهتاب | 29. سام | 30. لیدا""" await send_with_keyboard(client, chat_id, speakers_menu, False) return elif current_mode == "tts_waiting_for_speaker": normalized_text = to_english_digits(user_text_str) if normalized_text.isdigit() and normalized_text in SPEAKERS: spk_name, spk_id = SPEAKERS[normalized_text] txt = user_states[str_chat_id]["text"] user_states[str_chat_id]["mode"] = "tts_waiting_for_text" asyncio.create_task(process_tts(client, chat_id, txt, spk_id, spk_name)) else: await send_with_keyboard(client, chat_id, "❌ شماره نامعتبر است! لطفاً فقط یک عدد بین 1 تا 30 بفرستید.", False) return elif current_mode == "podcast_waiting_for_topic": if user_text_str: asyncio.create_task(process_podcast(client, chat_id, user_text_str)) else: await send_with_keyboard(client, chat_id, "⚠️ لطفاً موضوع پادکست خود را به صورت متنی بفرستید.", False) return elif current_mode == "image_waiting_for_file": if not is_file: return await send_with_keyboard(client, chat_id, "⚠️ لطفاً ابتدا یک تصویر ارسال کنید.", False) await send_with_keyboard(client, chat_id, "📥 در حال دریافت تصویر...", False) try: file_bytes = await helper_download_file(client, msg_obj) user_states[str_chat_id]["file_bytes"] = file_bytes user_states[str_chat_id]["file_name"] = file_name user_states[str_chat_id]["mode"] = "image_waiting_for_prompt" await send_with_keyboard(client, chat_id, "✅ تصویر با موفقیت دریافت شد.\n\nحالا لطفاً متنی بگویید **چگونه تحلیل شود؟**\n(می‌توانید سوال خاصی بپرسید یا فقط بخواهید تصویر را توصیف کند)", False) except Exception as dl_err: await send_with_keyboard(client, chat_id, f"❌ خطا در دریافت تصویر!\n{str(dl_err)}", False) return elif current_mode == "image_waiting_for_prompt": if user_text_str: saved_bytes = user_states[str_chat_id].get("file_bytes") saved_name = user_states[str_chat_id].get("file_name", "file.jpeg") user_states[str_chat_id]["mode"] = "image_waiting_for_file" asyncio.create_task(process_image_analysis(client, chat_id, saved_bytes, saved_name, user_text_str)) else: await send_with_keyboard(client, chat_id, "⚠️ لطفاً درخواست خود را متنی بنویسید.", False) return elif current_mode == "create_file_waiting_for_topic": if user_text_str: asyncio.create_task(process_create_file(client, chat_id, user_text_str)) else: await send_with_keyboard(client, chat_id, "⚠️ لطفاً موضوع مقاله خود را به صورت متنی بفرستید.", False) return elif current_mode == "vc_waiting_for_voice": if not is_file: return await send_with_keyboard(client, chat_id, "⚠️ لطفاً یک فایل صوتی یا ویس ارسال کنید.", False) await send_with_keyboard(client, chat_id, "📥 در حال دانلود فایل صوتی شما...", False) try: file_bytes = await helper_download_file(client, msg_obj) user_states[str_chat_id]["file_bytes"] = file_bytes user_states[str_chat_id]["mode"] = "vc_waiting_for_model" model_menu = "✅ صدای شما دریافت شد.\n\nلطفا **شماره** مدلی که می‌خواهید صدایتان به آن تبدیل شود را ارسال کنید:\n\n" for k, v in LEGACY_MODELS.items(): if k == "8": model_menu += f"{k}. {v['name']} (نامحدود رایگان)\n" else: model_menu += f"{k}. {v['name']}\n" model_menu += "➖➖➖➖➖➖➖➖\n" for k, v in STANDARD_MODELS.items(): model_menu += f"{k}. {v['name']}\n" await send_with_keyboard(client, chat_id, model_menu, False) except Exception as dl_err: await send_with_keyboard(client, chat_id, f"❌ خطا در دریافت فایل!\n{str(dl_err)}", False) return elif current_mode == "vc_waiting_for_model": choice = to_english_digits(user_text_str).strip() if choice in STANDARD_MODELS: if creds["voice_conv"] <= 0: return await send_with_keyboard(client, chat_id, "❌ سهمیه تغییر صدای شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False) user_states[str_chat_id]["mode"] = None model = STANDARD_MODELS[choice] src_bytes = user_states[str_chat_id]["file_bytes"] await send_with_keyboard(client, chat_id, "📥 در حال آماده‌سازی فایل‌ها...", False) ref_bytes = await helper_download_url_to_bytes(model["ref"]) if not ref_bytes: return await send_with_keyboard(client, chat_id, "❌ خطا در دسترسی به فایل صدای مدل.", False) asyncio.create_task(process_standard_vc_job(client, chat_id, src_bytes, ref_bytes, model["name"], "voice_conv")) elif choice in LEGACY_MODELS: if choice != "8" and creds["voice_conv"] <= 0: return await send_with_keyboard(client, chat_id, "❌ سهمیه تغییر صدای شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False) user_states[str_chat_id]["selected_model"] = choice user_states[str_chat_id]["mode"] = "vc_waiting_for_gender" gender_msg = f"✅ مدل {LEGACY_MODELS[choice]['name']} انتخاب شد.\n\nبرای تنظیم دقیق فرکانس‌ها به ما بگویید صدایی که خودتان فرستادید صدای یک **مرد** است یا **زن**؟\n\n1. مرد 👨\n2. زن 👩" await send_with_keyboard(client, chat_id, gender_msg, False) else: await send_with_keyboard(client, chat_id, "❌ شماره وارد شده صحیح نیست. لطفاً فقط عدد مدل را بفرستید.", False) return elif current_mode == "vc_waiting_for_gender": choice = to_english_digits(user_text_str).strip() if choice not in["1", "2"]: return await send_with_keyboard(client, chat_id, "❌ لطفاً عدد 1 (مرد) یا 2 (زن) را ارسال کنید.", False) user_gender = "male" if choice == "1" else "female" model_key = user_states[str_chat_id]["selected_model"] model = LEGACY_MODELS[model_key] target_gender = model["gender"] pitch = 0 if target_gender == "female" and user_gender == "male": pitch = 12 elif target_gender == "male" and user_gender == "female": pitch = -12 user_states[str_chat_id]["mode"] = None src_bytes = user_states[str_chat_id]["file_bytes"] asyncio.create_task(process_legacy_vc_job(client, chat_id, src_bytes, model["url"], pitch, model["name"])) return elif current_mode == "clone_waiting_for_src": if not is_file: return await send_with_keyboard(client, chat_id, "⚠️ لطفاً فایل صوتی صدای خودتان را بفرستید.", False) await send_with_keyboard(client, chat_id, "📥 در حال دانلود صدای شما...", False) try: file_bytes = await helper_download_file(client, msg_obj) user_states[str_chat_id]["file_bytes"] = file_bytes user_states[str_chat_id]["mode"] = "clone_waiting_for_ref" await send_with_keyboard(client, chat_id, "✅ صدای شما دریافت شد.\n\nحالا **فایل الگو (صدای شخصی که می‌خواهید شبیه‌سازی کنید)** را ارسال کنید.\n(پیشنهاد: فایل بدون نویز و موسیقی، بین ۳ تا ۱۰ ثانیه)", False) except Exception as dl_err: await send_with_keyboard(client, chat_id, f"❌ خطا در دریافت فایل!\n{str(dl_err)}", False) return elif current_mode == "clone_waiting_for_ref": if not is_file: return await send_with_keyboard(client, chat_id, "⚠️ لطفاً فایل صوتی الگوی هدف را بفرستید.", False) await send_with_keyboard(client, chat_id, "📥 در حال دانلود صدای الگو...", False) try: ref_bytes = await helper_download_file(client, msg_obj) if creds["voice_clone"] <= 0: return await send_with_keyboard(client, chat_id, "❌ سهمیه کلون کردن صدای شما تمام شده است. نیازمند تهیه اشتراک هستید.", False) user_states[str_chat_id]["mode"] = None src_bytes = user_states[str_chat_id]["file_bytes"] asyncio.create_task(process_standard_vc_job(client, chat_id, src_bytes, ref_bytes, "صدای اختصاصی (کلون شده)", "voice_clone")) except Exception as dl_err: await send_with_keyboard(client, chat_id, f"❌ خطا در دریافت فایل!\n{str(dl_err)}", False) return except Exception: traceback.print_exc() if __name__ == "__main__": threading.Thread(target=run_flask, daemon=True).start() if bot_token: loop = asyncio.get_event_loop() loop.set_default_executor(concurrent.futures.ThreadPoolExecutor(max_workers=256)) loop.create_task(ram_garbage_collector()) print("🚀 ربات آلفا پرو (نسخه صنعتی + دیتابیس هوشمند + فیلتر زمان قطعی + 256 تونل همزمان + سیستم ویدیو) روشن شد...") bot.run()