Spaces:
Paused
Paused
| # ============================================================================== | |
| # 🟢 پارت 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__) | |
| 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) | |
| 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 | |
| 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) | |
| 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() |