Opera8 commited on
Commit
1a1634e
·
verified ·
1 Parent(s): d09aa4b

Upload main (پاکسازی).py

Browse files
Files changed (1) hide show
  1. main (پاکسازی).py +1883 -0
main (پاکسازی).py ADDED
@@ -0,0 +1,1883 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ==============================================================================
2
+ # 🟢 پارت 1: وارد کردن کتابخانه‌ها و ماژول‌ها (Imports)
3
+ # ==============================================================================
4
+ import os
5
+ import time
6
+ import threading
7
+ import random
8
+ import aiohttp
9
+ import traceback
10
+ import asyncio
11
+ import base64
12
+ import mimetypes
13
+ import io
14
+ import json
15
+ import datetime
16
+ import string
17
+ import uuid
18
+ import concurrent.futures
19
+ import sqlite3 # 🟢 اضافه شده برای دیتابیس فوق‌سریع
20
+ import copy # 🟢 اضافه شده برای سیستم تشخیص تغییرات دیتابیس
21
+ from flask import Flask
22
+ from rubpy.bot import BotClient, filters
23
+
24
+ from huggingface_hub import AsyncInferenceClient, HfApi, hf_hub_download
25
+ from PIL import Image
26
+ from pydub import AudioSegment
27
+
28
+ # ==============================================================================
29
+ # 🟢 پارت 2: تنظیمات سرورها، مدل‌های تغییر صدا و کلون صدا
30
+ # ==============================================================================
31
+ # --- تنظیمات آدرس سرورهای تغییر صدا ---
32
+ VC_BASE_URL = "https://sada8888-sada.hf.space"
33
+ LEGACY_BASE_URL = "https://ezmarynoori-rvc.hf.space"
34
+
35
+ LEGACY_MODELS = {
36
+ "1": {"name": "شادمهر", "url": "https://huggingface.co/amirmatrix/shadmehr/resolve/main/added_IVF722_Flat_nprobe_1_shadmehr_v2.zip?download=true", "gender": "male"},
37
+ "2": {"name": "معین", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Moein.zip?download=true", "gender": "male"},
38
+ "3": {"name": "بیلی آیلیش", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Billie.zip?download=true", "gender": "female"},
39
+ "4": {"name": "محسن چاوشی", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/chavoshi250.zip?download=true", "gender": "male"},
40
+ "5": {"name": "سیاوش قمیشی", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Ghomayshi250.zip?download=true", "gender": "male"},
41
+ "6": {"name": "یاس", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Yas300.zip?download=true", "gender": "male"},
42
+ "7": {"name": "عادل فردوسی‌پور", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Adel.zip?download=true", "gender": "male"},
43
+ "8": {"name": "باب اسفنجی", "url": "https://huggingface.co/datasets/Hamed744/Ezmary/resolve/main/Bab_Asfanj300.zip?download=true", "gender": "male"}
44
+ }
45
+
46
+ STANDARD_MODELS = {
47
+ "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"},
48
+ "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"},
49
+ "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"},
50
+ "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"}
51
+ }
52
+
53
+ # ==============================================================================
54
+ # 🟢 پارت 3: متغیرهای ادمین، سیستم ضد اسپم و توابع تبدیل تاریخ
55
+ # ==============================================================================
56
+ # --- کد مدیریت ---
57
+ ADMIN_CODE = "3011"
58
+ BOT_GUID = None
59
+
60
+ # =======================================================
61
+ # سیستم ضد اسپم کاربر-محور
62
+ # =======================================================
63
+ user_message_times = {}
64
+
65
+ def is_user_spamming(chat_id):
66
+ now = time.time()
67
+ times = user_message_times.get(chat_id,[])
68
+ times =[t for t in times if now - t < 3.0]
69
+ times.append(now)
70
+ user_message_times[chat_id] = times
71
+ if len(times) > 4:
72
+ return True
73
+ return False
74
+
75
+ def gregorian_to_jalali(gy, gm, gd):
76
+ g_d_m =[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
77
+ gy2 = (gy + 1) if (gm > 2) else gy
78
+ days = 355666 + (365 * gy) + ((gy2 + 3) // 4) - ((gy2 + 99) // 100) + ((gy2 + 399) // 400) + gd + g_d_m[gm - 1]
79
+ jy = -1595 + (33 * (days // 12053))
80
+ days %= 12053
81
+ jy += 4 * (days // 1461)
82
+ days %= 1461
83
+ if days > 365:
84
+ jy += (days - 1) // 365
85
+ days = (days - 1) % 365
86
+ if days < 186:
87
+ jm = 1 + (days // 31)
88
+ jd = 1 + (days % 31)
89
+ else:
90
+ jm = 7 + ((days - 186) // 30)
91
+ jd = 1 + ((days - 186) % 30)
92
+ return jy, jm, jd
93
+
94
+ # ==============================================================================
95
+ # 🟢 پارت 4: دیتابیس SQLite (نسخه نجات همه‌جانبه: اعتبارها + پیام‌های قدیمی)
96
+ # ==============================================================================
97
+ import os
98
+ import sqlite3
99
+ import json
100
+ import copy
101
+
102
+ # استفاده از نام جدید v3 برای شروع کاملاً پاک و بدون قفل
103
+ DB_FILE = "/data/users_v3.db"
104
+ CORRUPT_FILE = "/data/users_db.db" # فایل ۱۱.۷ مگابایتی شما
105
+ BACKUP_JSON = "/data/users_db.json.bak" # فایل ۴.۷ مگابایتی اعتبارها
106
+
107
+ last_saved_state = {}
108
+
109
+ def init_sqlite_db():
110
+ try:
111
+ os.makedirs(os.path.dirname(DB_FILE), exist_ok=True)
112
+
113
+ # اگر فایل v3 وجود ندارد، یعنی بار اول است و باید نجات را شروع کنیم
114
+ is_first_run = not os.path.exists(DB_FILE)
115
+
116
+ conn = sqlite3.connect(DB_FILE, timeout=60.0)
117
+ cursor = conn.cursor()
118
+ cursor.execute('PRAGMA journal_mode=DELETE;') # امن‌ترین حالت برای شبکه
119
+ cursor.execute('CREATE TABLE IF NOT EXISTS users (chat_id TEXT PRIMARY KEY, user_data TEXT)')
120
+ cursor.execute('CREATE TABLE IF NOT EXISTS processed_messages (message_id TEXT PRIMARY KEY)')
121
+ conn.commit()
122
+ conn.close()
123
+
124
+ if is_first_run:
125
+ print("🚨 عملیات نجات آغاز شد...")
126
+ # ۱. بازیابی اعتبارها از JSON (۱۰۰ درصد مطمئن)
127
+ restore_users_from_json()
128
+ # ۲. نجات شناسه‌های پیام از فایل ۱۱.۷ مگابایتی (تا جایی که راه بدهد)
129
+ salvage_messages_from_corrupt_db()
130
+
131
+ except Exception as e:
132
+ print(f"❌ خطا در راه اندازی دیتابیس: {e}")
133
+
134
+ def restore_users_from_json():
135
+ if os.path.exists(BACKUP_JSON):
136
+ print("📥 در حال بازیابی اعتبار کاربران از فایل پشتیبان...")
137
+ try:
138
+ with open(BACKUP_JSON, "r", encoding="utf-8") as f:
139
+ data = json.load(f)
140
+ if data:
141
+ conn = sqlite3.connect(DB_FILE, timeout=60.0)
142
+ for cid, udata in data.items():
143
+ conn.execute("INSERT OR REPLACE INTO users VALUES (?, ?)", (str(cid), json.dumps(udata)))
144
+ conn.commit()
145
+ conn.close()
146
+ print(f"✅ اعتبار {len(data)} کاربر با موفقیت بازیابی شد.")
147
+ except Exception as e:
148
+ print(f"❌ خطا در بازیابی اعتبارها: {e}")
149
+
150
+ def salvage_messages_from_corrupt_db():
151
+ if os.path.exists(CORRUPT_FILE):
152
+ print(f"🔍 در حال تلاش برای نجات شناسه‌های پیام از فایل {CORRUPT_FILE} ...")
153
+ try:
154
+ # وصل کردن فایل خراب به دیتابیس جدید
155
+ conn = sqlite3.connect(DB_FILE, timeout=60.0)
156
+ conn.execute(f"ATTACH DATABASE '{CORRUPT_FILE}' AS old_db")
157
+
158
+ # کپی کردن شناسه‌ها (با استفاده از INSERT OR IGNORE که اگر خراب بود رد شود)
159
+ # این دستور تلاش می‌کند حتی اگر فایل Malformed باشد، ردیف‌های سالم را بردارد
160
+ conn.execute("INSERT OR IGNORE INTO processed_messages SELECT message_id FROM old_db.processed_messages")
161
+
162
+ conn.commit()
163
+ conn.close()
164
+ print("✅ شناسه‌های پیام قدیمی با موفقیت نجات یافتند و به دیتابیس جدید منتقل شدند.")
165
+ except Exception as e:
166
+ print(f"⚠️ هشدار: برخی پیام‌ها به دلیل خرابی شدید فایل اصلی قابل نجات نبودند، اما نگران نباشید، ربات دوباره آن‌ها را شناسایی می‌کند. ارور: {e}")
167
+
168
+ def load_db():
169
+ global last_saved_state
170
+ init_sqlite_db()
171
+ db_dict = {}
172
+ try:
173
+ conn = sqlite3.connect(DB_FILE, timeout=60.0)
174
+ cursor = conn.cursor()
175
+ cursor.execute("SELECT chat_id, user_data FROM users")
176
+ for row in cursor.fetchall():
177
+ db_dict[row[0]] = json.loads(row[1])
178
+ conn.close()
179
+ last_saved_state = copy.deepcopy(db_dict)
180
+ print(f"🚀 دیتابیس نهایی آماده شد. تعداد کاربران: {len(db_dict)}")
181
+ return db_dict
182
+ except Exception as e:
183
+ print(f"⚠️ ارور در لود نهایی: {e}")
184
+ return {}
185
+
186
+ def save_db(db_data):
187
+ global last_saved_state
188
+ try:
189
+ changed = []
190
+ for cid, data in db_data.items():
191
+ if cid not in last_saved_state or last_saved_state[cid] != data:
192
+ changed.append((str(cid), json.dumps(data, ensure_ascii=False)))
193
+ if not changed: return
194
+ conn = sqlite3.connect(DB_FILE, timeout=60.0)
195
+ conn.execute('PRAGMA journal_mode=DELETE;')
196
+ conn.executemany("INSERT OR REPLACE INTO users (chat_id, user_data) VALUES (?, ?)", changed)
197
+ conn.commit()
198
+ conn.close()
199
+ for cid, _ in changed:
200
+ last_saved_state[cid] = copy.deepcopy(db_data[cid])
201
+ except Exception as e:
202
+ print(f"❌ خطا در ذخیره: {e}")
203
+
204
+ def is_message_processed(message_id):
205
+ try:
206
+ conn = sqlite3.connect(DB_FILE, timeout=30.0)
207
+ res = conn.execute("SELECT 1 FROM processed_messages WHERE message_id = ?", (str(message_id),)).fetchone()
208
+ conn.close()
209
+ return res is not None
210
+ except: return False
211
+
212
+ def mark_message_processed(message_id):
213
+ try:
214
+ conn = sqlite3.connect(DB_FILE, timeout=30.0)
215
+ conn.execute("INSERT OR IGNORE INTO processed_messages VALUES (?)", (str(message_id),))
216
+ conn.commit()
217
+ conn.close()
218
+ except: pass
219
+
220
+ user_credits_db = load_db()
221
+
222
+
223
+ # ==============================================================================
224
+ # 🟢 پارت 5: توابع کد معرف، مدیریت سهمیه حساب کاربری و تبدیل اعداد
225
+ # ==============================================================================
226
+ def get_or_create_referral_code(chat_id):
227
+ user_data = user_credits_db[chat_id]
228
+ if not user_data.get("referral_code"):
229
+ while True:
230
+ new_code = ''.join(random.choices(string.digits, k=8))
231
+ exists = any(isinstance(u, dict) and u.get("referral_code") == new_code for u in user_credits_db.values())
232
+ if not exists:
233
+ user_data["referral_code"] = new_code
234
+ save_db(user_credits_db)
235
+ break
236
+ return user_data["referral_code"]
237
+
238
+ def find_user_by_referral_code(code):
239
+ code = code.strip()
240
+ for uid, data in user_credits_db.items():
241
+ if isinstance(data, dict) and data.get("referral_code", "") == code:
242
+ return uid
243
+ return None
244
+
245
+ def get_user_credits(chat_id):
246
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
247
+ today_str = datetime.date.today().isoformat()
248
+
249
+ if str_chat_id not in user_credits_db or not isinstance(user_credits_db[str_chat_id], dict):
250
+ user_credits_db[str_chat_id] = {
251
+ "is_premium": False,
252
+ "expire_date": None,
253
+ "last_reset": "",
254
+ "chat": 10,
255
+ "image": 5,
256
+ "edit_image": 1,
257
+ "podcast": 2,
258
+ "tts": 5,
259
+ "file": 1,
260
+ "stt": 5,
261
+ "voice_conv": 3,
262
+ "voice_clone": 1,
263
+ "last_msg_id": 0,
264
+ "has_joined": False,
265
+ "invited_count": 0,
266
+ "used_referral": False,
267
+ "referral_code": ""
268
+ }
269
+ save_db(user_credits_db)
270
+
271
+ user_data = user_credits_db[str_chat_id]
272
+
273
+ if "voice_conv" not in user_data: user_data["voice_conv"] = 3
274
+ if "voice_clone" not in user_data: user_data["voice_clone"] = 1
275
+ if "last_msg_id" not in user_data: user_data["last_msg_id"] = 0
276
+
277
+ is_premium = user_data.get("is_premium", False)
278
+
279
+ if is_premium and user_data.get("expire_date"):
280
+ try:
281
+ expire_date = datetime.datetime.fromisoformat(user_data["expire_date"])
282
+ if datetime.datetime.now() > expire_date:
283
+ user_data["is_premium"] = False
284
+ user_data["expire_date"] = None
285
+ is_premium = False
286
+ except Exception:
287
+ pass
288
+
289
+ if not is_premium:
290
+ if user_data.get("last_reset") != today_str:
291
+ user_data["last_reset"] = today_str
292
+ user_data["chat"] = 10
293
+ user_data["image"] = 5
294
+ user_data["edit_image"] = 1
295
+ user_data["podcast"] = 2
296
+ user_data["tts"] = 5
297
+ user_data["file"] = 1
298
+ user_data["stt"] = 5
299
+ user_data["voice_conv"] = 3
300
+ user_data["voice_clone"] = 1
301
+ save_db(user_credits_db)
302
+
303
+ return user_data
304
+
305
+ def to_english_digits(text):
306
+ if not text: return text
307
+ persian_digits = '۰۱۲۳۴۵۶۷۸۹'
308
+ arabic_digits = '٠١٢٣٤٥٦٧٨٩'
309
+ english_digits = '0123456789'
310
+ translation_table = str.maketrans(persian_digits + arabic_digits, english_digits * 2)
311
+ return str(text).translate(translation_table)
312
+
313
+ # ==============================================================================
314
+ # 🟢 پارت 6: تنظیمات وب‌سرور (Flask) و توابع کمکی فایل‌ها
315
+ # ==============================================================================
316
+ # --- تنظیمات وب سرور ---
317
+ app = Flask(__name__)
318
+
319
+ @app.route('/')
320
+ def home():
321
+ return "ربات یکپارچه آلفا (نسخه پرو + مدیریت اشتراک نامحدود + دعوت دوستان + سیستم تغییر صدا) روشن است! 🚀"
322
+
323
+ def run_flask():
324
+ app.run(host="0.0.0.0", port=7860, threaded=True)
325
+
326
+ # --- توابع کمکی ---
327
+ def sync_save_image(image, file_name):
328
+ rgb_im = image.convert('RGB')
329
+ rgb_im.save(file_name, format="JPEG", quality=100)
330
+
331
+ def sync_write_file(file_name, data):
332
+ with open(file_name, "wb") as f:
333
+ f.write(data)
334
+
335
+ def sync_read_file(file_name):
336
+ with open(file_name, 'rb') as f:
337
+ return f.read()
338
+
339
+ def sync_combine_audio(current_audio, new_bytes):
340
+ audio_segment = AudioSegment.from_file(io.BytesIO(new_bytes))
341
+ return current_audio + audio_segment
342
+
343
+ def sync_export_audio(audio_obj, file_name):
344
+ audio_obj.export(file_name, format="mp3")
345
+
346
+ # ==============================================================================
347
+ # 🟢 پارت 7: کیبوردها (دکمه‌های شیشه‌ای / منوها)
348
+ # ==============================================================================
349
+ # --- ساختار کیبورد ---
350
+ MAIN_KEYPAD_DICT = {
351
+ "rows":[
352
+ {
353
+ "buttons":[
354
+ {"id": "chat_btn", "type": "Simple", "button_text": "چت با هوش مصنوعی 🤖"}
355
+ ]
356
+ },
357
+ {
358
+ "buttons":[
359
+ {"id": "img_btn", "type": "Simple", "button_text": "ساخت تصاویر🎨"},
360
+ {"id": "edit_img_btn", "type": "Simple", "button_text": "ویرایش تصاویر 🪄"}
361
+ ]
362
+ },
363
+ {
364
+ "buttons":[
365
+ {"id": "podcast_btn", "type": "Simple", "button_text": "ساخت پادکست 🎙️"},
366
+ {"id": "tts_btn", "type": "Simple", "button_text": "تبدیل متن به صدا🗣️"}
367
+ ]
368
+ },
369
+ {
370
+ "buttons":[
371
+ {"id": "vc_btn", "type": "Simple", "button_text": "تغییر صدا 🎙️"},
372
+ {"id": "clone_btn", "type": "Simple", "button_text": "کلون کردن صدا 👤"}
373
+ ]
374
+ },
375
+ {
376
+ "buttons":[
377
+ {"id": "stt_btn", "type": "Simple", "button_text": "فایل صوتی به متن 📝"},
378
+ {"id": "file_btn", "type": "Simple", "button_text": "تحلیل فایل 📁"}
379
+ ]
380
+ },
381
+ {
382
+ "buttons":[
383
+ {"id": "create_file_btn", "type": "Simple", "button_text": "ساخت فایل 📄"}
384
+ ]
385
+ },
386
+ {
387
+ "buttons":[
388
+ {"id": "account_btn", "type": "Simple", "button_text": "حساب کاربری 👤"},
389
+ {"id": "buy_btn", "type": "Simple", "button_text": "خرید اشتراک 💎"}
390
+ ]
391
+ },
392
+ {
393
+ "buttons":[
394
+ {"id": "invite_btn", "type": "Simple", "button_text": "دعوت دوستان 🎁"},
395
+ {"id": "referral_btn", "type": "Simple", "button_text": "ثبت کد هدیه 🎫"}
396
+ ]
397
+ },
398
+ {
399
+ "buttons":[
400
+ {"id": "transfer_btn", "type": "Simple", "button_text": "انتقال اکانت از برنامه به ربات"}
401
+ ]
402
+ },
403
+ {
404
+ "buttons":[
405
+ {"id": "cancel_btn", "type": "Simple", "button_text": "برگشت♻️"}
406
+ ]
407
+ }
408
+ ],
409
+ "resize_keyboard": True
410
+ }
411
+
412
+ CHANNEL_USERNAME = "aialpha"
413
+ CHANNEL_GUID = None
414
+
415
+ JOIN_KEYPAD_DICT = {
416
+ "rows":[
417
+ {
418
+ "buttons":[
419
+ {"id": "check_join_btn", "type": "Simple", "button_text": "✅ عضو شدم"}
420
+ ]
421
+ }
422
+ ],
423
+ "resize_keyboard": True
424
+ }
425
+
426
+ # ==============================================================================
427
+ # 🟢 پارت 8: تابع بررسی عضویت کانال، ارسال کیبورد و توکن روبیکا
428
+ # ==============================================================================
429
+ async def check_channel_membership(client, user_id):
430
+ global CHANNEL_GUID
431
+ if str(user_id) == str(BOT_GUID): return True
432
+
433
+ try:
434
+ if not CHANNEL_GUID:
435
+ res = await client.get_object_by_username(CHANNEL_USERNAME)
436
+ if isinstance(res, dict):
437
+ if 'channel' in res and 'channel_guid' in res['channel']:
438
+ CHANNEL_GUID = res['channel']['channel_guid']
439
+ elif 'data' in res and 'channel' in res['data']:
440
+ CHANNEL_GUID = res['data']['channel']['channel_guid']
441
+ elif 'exist' in res and 'chat' in res['exist']:
442
+ CHANNEL_GUID = res['exist']['chat']['object_guid']
443
+ else:
444
+ if hasattr(res, 'channel'):
445
+ CHANNEL_GUID = getattr(res.channel, 'channel_guid', None)
446
+ elif hasattr(res, 'data') and hasattr(res.data, 'channel'):
447
+ CHANNEL_GUID = getattr(res.data.channel, 'channel_guid', None)
448
+ elif hasattr(res, 'exist') and hasattr(res.exist, 'chat'):
449
+ CHANNEL_GUID = getattr(res.exist.chat, 'object_guid', None)
450
+
451
+ if not CHANNEL_GUID: return True
452
+
453
+ payload = {"channel_guid": CHANNEL_GUID, "member_guid": user_id}
454
+ res = await client._make_request("getChannelParticipant", payload)
455
+
456
+ if isinstance(res, dict) and res.get("status") == "OK":
457
+ data = res.get("data", {})
458
+ if data and "participant" in data: return True
459
+ elif hasattr(res, 'status') and getattr(res, 'status') == "OK":
460
+ return True
461
+ return False
462
+ except Exception: return False
463
+
464
+ async def send_with_keyboard(client, chat_id, text, use_keyboard=True):
465
+ try:
466
+ if not use_keyboard: return await client.send_message(chat_id, text)
467
+ payload = {"chat_id": chat_id, "text": text, "chat_keypad_type": "New", "chat_keypad": MAIN_KEYPAD_DICT}
468
+ return await client._make_request("sendMessage", payload)
469
+ except Exception:
470
+ try: return await client.send_message(chat_id, text)
471
+ except Exception: return None
472
+
473
+ bot_token = os.environ.get("RUBIKA_AUTH", "").strip()
474
+
475
+ # ==============================================================================
476
+ # 🟢 پارت 9: دانلودر قدرتمند و تابع ضد قطعی روبیکا
477
+ # ==============================================================================
478
+ # ==================================================================
479
+ # تابع دانلود ضد بمب اتم (پافشاری ۳۵ بار)
480
+ # ==================================================================
481
+ async def helper_download_file(client, msg_obj):
482
+ errors =[]
483
+ file_obj = None
484
+ file_id = None
485
+
486
+ for attr in['file', 'file_inline', 'photo', 'voice', 'audio', 'document', 'video']:
487
+ val = getattr(msg_obj, attr, None)
488
+ if val:
489
+ file_obj = val
490
+ if hasattr(val, 'file_id'): file_id = val.file_id
491
+ elif isinstance(val, dict) and 'file_id' in val: file_id = val['file_id']
492
+ break
493
+
494
+ if not file_obj and hasattr(msg_obj, "file_id"):
495
+ file_id = msg_obj.file_id
496
+ file_obj = msg_obj
497
+
498
+ if not file_id: raise Exception("خطا: هیچ فایلی در پیام یافت نشد.")
499
+
500
+ temp_name = f"temp_dl_{uuid.uuid4().hex}.tmp"
501
+
502
+ for attempt in range(20):
503
+ try:
504
+ url_get_file = f"https://botapi.rubika.ir/v3/{bot_token}/getFile"
505
+ payload = {"file_id": str(file_id)}
506
+ headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
507
+
508
+ async with aiohttp.ClientSession() as session:
509
+ async with session.post(url_get_file, json=payload, headers=headers, timeout=30) as resp:
510
+ if resp.status == 200:
511
+ res_data = await resp.json()
512
+ if res_data.get("status") == "OK" or res_data.get("status_det") == "OK":
513
+ download_url = res_data.get("data", {}).get("download_url") or res_data.get("data", {}).get("file_url")
514
+ if download_url:
515
+ for dl_attempt in range(3):
516
+ try:
517
+ async with session.get(download_url, headers=headers, timeout=60) as dl_resp:
518
+ if dl_resp.status == 200:
519
+ data = await dl_resp.read()
520
+ if data and len(data) > 0: return data
521
+ else: errors.append(f"DL HTTP {dl_resp.status}")
522
+ await asyncio.sleep(2)
523
+ except Exception as dl_err:
524
+ errors.append(f"DL Error: {str(dl_err)[:30]}")
525
+ await asyncio.sleep(2)
526
+ else: errors.append("No download URL in response.")
527
+ else: errors.append(f"API Not OK: {res_data.get('status')}")
528
+ elif resp.status in [502, 503, 500]:
529
+ errors.append(f"GetFile HTTP {resp.status}")
530
+ await asyncio.sleep(3.5)
531
+ continue
532
+ else:
533
+ errors.append(f"GetFile HTTP {resp.status}")
534
+ await asyncio.sleep(2)
535
+ except Exception as e:
536
+ errors.append(f"API Error: {str(e)[:50]}")
537
+ await asyncio.sleep(2)
538
+
539
+ if file_obj:
540
+ for attempt in range(10):
541
+ try:
542
+ if hasattr(client, "download"):
543
+ result = await client.download(file_obj, save_as=temp_name)
544
+ if isinstance(result, bytes) and len(result) > 0: return result
545
+ if os.path.exists(temp_name):
546
+ data = await asyncio.to_thread(sync_read_file, temp_name)
547
+ os.remove(temp_name)
548
+ if data and len(data) > 0: return data
549
+ except Exception as e:
550
+ errors.append(f"Rubpy Obj Error: {str(e)[:50]}")
551
+ await asyncio.sleep(3)
552
+
553
+ for attempt in range(5):
554
+ try:
555
+ if hasattr(client, "download_file"):
556
+ await client.download_file(file_id, file_name=temp_name)
557
+ if os.path.exists(temp_name):
558
+ data = await asyncio.to_thread(sync_read_file, temp_name)
559
+ os.remove(temp_name)
560
+ if data and len(data) > 0: return data
561
+ except Exception as e:
562
+ errors.append(f"Rubpy FileId Error: {str(e)[:50]}")
563
+ await asyncio.sleep(3)
564
+
565
+ raise Exception(f"سرورهای دانلود روبیکا پس از ۳۵ بار تلاش پاسخ ندادند!\nلاگ خطاها: {str(errors[-5:])}")
566
+
567
+ async def helper_download_url_to_bytes(url):
568
+ async with aiohttp.ClientSession() as session:
569
+ for _ in range(3):
570
+ try:
571
+ async with session.get(url, timeout=30) as resp:
572
+ if resp.status == 200:
573
+ return await resp.read()
574
+ except Exception:
575
+ await asyncio.sleep(2)
576
+ return None
577
+
578
+ # ==============================================================================
579
+ # 🟢 پارت 10: سیستم چرخش کلیدهای جیمینای و تنظیم توکن‌های هاگینگ فیس
580
+ # ==============================================================================
581
+ # ==================================================================
582
+ # توکن‌ها و کلیدها
583
+ # ==================================================================
584
+ GEMINI_KEYS_STR1 = os.environ.get("GEMINI_API_KEYS1", "")
585
+ GEMINI_KEYS_STR2 = os.environ.get("GEMINI_API_KEYS2", "")
586
+
587
+ _raw_keys =[]
588
+ if GEMINI_KEYS_STR1: _raw_keys.extend(GEMINI_KEYS_STR1.split(","))
589
+ if GEMINI_KEYS_STR2: _raw_keys.extend(GEMINI_KEYS_STR2.split(","))
590
+
591
+ GEMINI_KEYS = list(set([k.strip() for k in _raw_keys if k.strip()]))
592
+ print(f"✅ تعداد {len(GEMINI_KEYS)} کلید جیمینای با موفقیت شناسایی شد.")
593
+
594
+ current_gemini_key_index = 0
595
+ gemini_key_lock = threading.Lock()
596
+
597
+ def get_next_gemini_keys(count=100):
598
+ global current_gemini_key_index
599
+ with gemini_key_lock:
600
+ total_keys = len(GEMINI_KEYS)
601
+ if total_keys == 0: return[]
602
+ actual_count = min(count, total_keys)
603
+ selected_keys =[]
604
+ for _ in range(actual_count):
605
+ selected_keys.append(GEMINI_KEYS[current_gemini_key_index])
606
+ current_gemini_key_index = (current_gemini_key_index + 1) % total_keys
607
+ return selected_keys
608
+
609
+ HF_TOKENS_STR = os.environ.get("HF_TOKENS", "")
610
+ HF_TOKENS =[k.strip() for k in HF_TOKENS_STR.split(",") if k.strip()]
611
+
612
+ # ==============================================================================
613
+ # 🟢 پارت 11: آپلودر قدرتمند و تابع ارسال فایل ضد خطا (نسخه نهایی ضد بمب اتم 🚀)
614
+ # ==============================================================================
615
+ async def helper_upload_file(client, chat_id, file_name, file_type="Image", caption=""):
616
+ abs_path = os.path.abspath(file_name)
617
+ error_logs = []
618
+
619
+ api_file_type = "Image" if file_type in["photo", "Image", "image"] else "Voice" if file_type in ["voice", "Voice", "audio"] else file_type
620
+
621
+ # روبیکا فرمت‌های خاصی را قبول می‌کند.
622
+ if api_file_type == "Voice" and not abs_path.lower().endswith('.ogg'):
623
+ api_file_type = "Music"
624
+ if api_file_type == "File" and abs_path.lower().endswith('.mp3'):
625
+ api_file_type = "Music"
626
+
627
+ # --- فاز اول (فوق سریع): استفاده از متدهای اختصاصی rubpy ---
628
+ for attempt in range(10):
629
+ try:
630
+ sent_success = False
631
+
632
+ # تلاش برای ارسال عکس
633
+ if api_file_type == "Image" and hasattr(client, "send_photo"):
634
+ try:
635
+ await client.send_photo(chat_id, photo=abs_path, caption=caption)
636
+ sent_success = True
637
+ except TypeError:
638
+ await client.send_photo(chat_id, abs_path, caption=caption)
639
+ sent_success = True
640
+
641
+ # تلاش برای ارسال ویس
642
+ elif api_file_type == "Voice" and hasattr(client, "send_voice"):
643
+ try:
644
+ await client.send_voice(chat_id, abs_path, caption=caption)
645
+ sent_success = True
646
+ except TypeError:
647
+ await client.send_voice(chat_id, file=abs_path, caption=caption)
648
+ sent_success = True
649
+
650
+ # تلاش برای ارسال موزیک
651
+ elif api_file_type == "Music" and hasattr(client, "send_music"):
652
+ try:
653
+ await client.send_music(chat_id, abs_path, caption=caption)
654
+ sent_success = True
655
+ except TypeError:
656
+ await client.send_music(chat_id, music=abs_path, caption=caption)
657
+ sent_success = True
658
+
659
+ # *** سیستم ضد بمب اتم فاز 1 ***
660
+ # اگر هیچکدام از متدهای بالا وجود نداشت یا به هر دلیلی ارسال نشد، حتما به عنوان فایل (سند) بفرست تا معطل نشود
661
+ if not sent_success:
662
+ if hasattr(client, "send_document"):
663
+ try:
664
+ await client.send_document(chat_id, document=abs_path, caption=caption)
665
+ sent_success = True
666
+ except TypeError:
667
+ await client.send_document(chat_id, abs_path, caption=caption)
668
+ sent_success = True
669
+ elif hasattr(client, "send_file"):
670
+ try:
671
+ await client.send_file(chat_id, file=abs_path)
672
+ except TypeError:
673
+ await client.send_file(chat_id, abs_path)
674
+ if caption:
675
+ await send_with_keyboard(client, chat_id, caption, True)
676
+ sent_success = True
677
+
678
+ if sent_success:
679
+ return True
680
+
681
+ except Exception as e:
682
+ err_msg = str(e)[:150]
683
+ error_logs.append(f"Rubpy Send Error: {err_msg}")
684
+
685
+ if "502" in err_msg or "timeout" in err_msg.lower() or "time" in err_msg.lower():
686
+ await asyncio.sleep(4)
687
+ else:
688
+ await asyncio.sleep(2)
689
+
690
+ # --- فاز دوم (سنگر آخر): آپلود دستی (Raw HTTP) در صورت مسدودی کامل روب‌پای ---
691
+ for attempt in range(15):
692
+ try:
693
+ url_request = f"https://botapi.rubika.ir/v3/{bot_token}/requestSendFile"
694
+ url_send = f"https://botapi.rubika.ir/v3/{bot_token}/sendFile"
695
+
696
+ async with aiohttp.ClientSession() as session:
697
+ async with session.post(url_request, json={"type": api_file_type}, timeout=30) as resp:
698
+ if resp.status != 200:
699
+ error_logs.append(f"Request HTTP {resp.status}")
700
+ await asyncio.sleep(2)
701
+ continue
702
+
703
+ req_data = await resp.json()
704
+ if req_data.get("status") == "OK":
705
+ upload_url = req_data.get("data", {}).get("upload_url")
706
+ if upload_url:
707
+ with open(abs_path, "rb") as f:
708
+ form = aiohttp.FormData()
709
+ form.add_field('file', f, filename=os.path.basename(abs_path))
710
+ async with session.post(upload_url, data=form, timeout=300) as up_resp:
711
+ if up_resp.status != 200:
712
+ error_logs.append(f"Upload HTTP {up_resp.status}")
713
+ if up_resp.status in[502, 503, 500]:
714
+ await asyncio.sleep(3)
715
+ else:
716
+ await asyncio.sleep(1.5)
717
+ continue
718
+
719
+ try:
720
+ up_data = await up_resp.json()
721
+ except Exception as json_err:
722
+ error_logs.append(f"JSON Decode Error: {json_err}")
723
+ await asyncio.sleep(2)
724
+ continue
725
+
726
+ final_file_id = None
727
+
728
+ # *** سیستم ضد بمب اتم فاز 2 ***
729
+ # دور زدن ارور روبیکا: اگر روبیکا به فرمت فایل گیر داد، سریعاً نوع را به File تغییر بده تا بدون بررسی فرمت آپلود کند!
730
+ if up_data.get("status") == "INVALID_INPUT" and "format" in str(up_data).lower():
731
+ api_file_type = "File"
732
+ error_logs.append(f"Format Blocked! Downgrading type to File.")
733
+ await asyncio.sleep(1)
734
+ continue
735
+
736
+ if up_data.get("status") == "OK" or up_data.get("status_det") == "OK":
737
+ if "data" in up_data and isinstance(up_data["data"], dict):
738
+ final_file_id = up_data["data"].get("file_id") or up_data["data"].get("id")
739
+ elif "file_id" in up_data:
740
+ final_file_id = up_data.get("file_id")
741
+
742
+ if final_file_id:
743
+ send_payload = {
744
+ "chat_id": str(chat_id),
745
+ "file_id": str(final_file_id),
746
+ "text": caption,
747
+ "chat_keypad_type": "New",
748
+ "chat_keypad": MAIN_KEYPAD_DICT
749
+ }
750
+ async with session.post(url_send, json=send_payload, timeout=30) as send_resp:
751
+ if send_resp.status != 200:
752
+ error_logs.append(f"Send HTTP {send_resp.status}")
753
+ await asyncio.sleep(2)
754
+ continue
755
+
756
+ s_data = await send_resp.json()
757
+ if s_data.get("status") == "OK": return True
758
+ else: error_logs.append(f"SendFile API Error: {s_data}")
759
+ else:
760
+ if up_data.get("status") == "INVALID_INPUT" and api_file_type in ["Voice", "Music"]:
761
+ api_file_type = "File" # دور زدن گیر دادن روبیکا
762
+ error_logs.append(f"Upload API Error: {up_data}")
763
+ else: error_logs.append(f"Missing upload_url in Response: {req_data}")
764
+ else: error_logs.append(f"Request API Error: {req_data}")
765
+ except Exception as e:
766
+ error_logs.append(f"Raw HTTP Error: {str(e)[:100]}")
767
+ await asyncio.sleep(2.5)
768
+
769
+ return "\n".join(error_logs[-6:])
770
+
771
+ # ==============================================================================
772
+ # 🟢 پارت 12: توابع تغییر صدا و لیست گویندگان
773
+ # ==============================================================================
774
+ # ==================================================================
775
+ # لیست‌های اولیه ربات
776
+ # ==================================================================
777
+ WORKER_URLS =["https://opera8-ttspro.hf.space/generate"]
778
+
779
+ SPEAKERS = {
780
+ "1": ("شهاب (مرد)", "Charon"), "2": ("آوا (زن)", "Zephyr"), "3": ("نوید (مرد)", "Achird"),
781
+ "4": ("آرمان (مرد)", "Zubenelgenubi"), "5": ("مهسا (زن)", "Vindemiatrix"), "6": ("دانا (مرد)", "Rasalgethi"),
782
+ "7": ("سامان (مرد)", "Sadachbia"), "8": ("آرش (مرد)", "Sadaltager"), "9": ("شبنم (زن)", "Sulafat"),
783
+ "10": ("سحر (زن)", "Laomedeia"), "11": ("مریم (زن)", "Achernar"), "12": ("بهرام (مرد)", "Alnilam"),
784
+ "13": ("نیکان (مرد)", "Schedar"), "14": ("فرناز (زن)", "Gacrux"), "15": ("سارا (زن)", "Pulcherrima"),
785
+ "16": ("مانی (مرد)", "Umbriel"), "17": ("آرتین (مرد)", "Algieba"), "18": ("دلنواز (زن)", "Despina"),
786
+ "19": ("روژان (زن)", "Erinome"), "20": ("امید (مرد)", "Algenib"), "21": ("بردیا (مرد)", "Orus"),
787
+ "22": ("ترانه (زن)", "Aoede"), "23": ("نیکو (زن)", "Callirrhoe"), "24": ("هستی (زن)", "Autonoe"),
788
+ "25": ("کامیار (مرد)", "Enceladus"), "26": ("کیانوش (مرد)", "Iapetus"), "27": ("پویا (مرد)", "Puck"),
789
+ "28": ("مهتاب (زن)", "Kore"), "29": ("سام (مرد)", "Fenrir"), "30": ("لیدا (زن)", "Leda")
790
+ }
791
+
792
+ user_states = {}
793
+
794
+ # ==================================================================
795
+ # توابع تغییر صدا و کلون کردن
796
+ # ==================================================================
797
+ async def process_standard_vc_job(client, chat_id, src_bytes, ref_bytes, job_type_name, credit_type):
798
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
799
+ creds = get_user_credits(str_chat_id)
800
+
801
+ proc_msg = await send_with_keyboard(client, chat_id, f"⏳ در حال آماده‌سازی فایل‌ها...\n(مدل: {job_type_name})", False)
802
+
803
+ async with aiohttp.ClientSession() as session:
804
+ form = aiohttp.FormData()
805
+ form.add_field('source_audio', src_bytes, filename='src.wav', content_type='audio/wav')
806
+ form.add_field('ref_audio', ref_bytes, filename='ref.wav', content_type='audio/wav')
807
+
808
+ try:
809
+ async with session.post(f"{VC_BASE_URL}/upload", data=form, timeout=43200) as resp:
810
+ if resp.status != 200:
811
+ return await send_with_keyboard(client, chat_id, f"❌ خطا در ارسال فایل‌ها (کد {resp.status})", True)
812
+ data = await resp.json()
813
+ job_id = data.get("job_id")
814
+ total_chunks = data.get("total_chunks", 1)
815
+ chunks = data.get("chunks",[])
816
+ except Exception as e:
817
+ return await send_with_keyboard(client, chat_id, f"❌ خطا در اتصال به سرور:\n{str(e)[:100]}", True)
818
+
819
+ await send_with_keyboard(client, chat_id, "✅ فایل‌ها ارسال شد. در حال پردازش و تغییر صدا...\n(لطفا چند دقیقه صبور باشید)", False)
820
+
821
+ final_filename = None
822
+ for _ in range(10000):
823
+ await asyncio.sleep(4)
824
+ payload_check = {"job_id": job_id, "total_chunks": total_chunks, "chunks": chunks}
825
+ try:
826
+ async with session.post(f"{VC_BASE_URL}/check_status", json=payload_check, timeout=20) as c_resp:
827
+ if c_resp.status == 200:
828
+ c_data = await c_resp.json()
829
+ if c_data.get("status") == "completed":
830
+ final_filename = c_data.get("filename")
831
+ break
832
+ elif c_data.get("status") in ["failed", "error"]:
833
+ return await send_with_keyboard(client, chat_id, "❌ خطای سرور در حین پردازش صدا.", True)
834
+ except Exception:
835
+ pass
836
+
837
+ if not final_filename:
838
+ return await send_with_keyboard(client, chat_id, "❌ پردازش بیش از حد طول کشید و متوقف شد.", True)
839
+
840
+ download_url = f"{VC_BASE_URL}/download/{final_filename}"
841
+ await send_with_keyboard(client, chat_id, "📥 پردازش تمام شد! در حال دانلود نتیجه...", False)
842
+ try:
843
+ async with session.get(download_url, timeout=43200) as d_resp:
844
+ if d_resp.status == 200:
845
+ result_bytes = await d_resp.read()
846
+ else:
847
+ return await send_with_keyboard(client, chat_id, "❌ خطا در دریافت فایل نهایی از سرور.", True)
848
+ except Exception:
849
+ return await send_with_keyboard(client, chat_id, "❌ خطا در اتصال هنگام دانلود خروجی.", True)
850
+
851
+ file_name_mp3 = f"vc_standard_{uuid.uuid4().hex[:6]}.mp3"
852
+ await asyncio.to_thread(sync_write_file, file_name_mp3, result_bytes)
853
+
854
+ upload_result = False
855
+ for up_att in range(3):
856
+ res = await helper_upload_file(client, chat_id, file_name_mp3, "Music", f"🎙️ {job_type_name} شما آماده است!")
857
+ if res is True:
858
+ upload_result = True
859
+ break
860
+ await asyncio.sleep(4)
861
+
862
+ if upload_result is True:
863
+ if not creds.get("is_premium"):
864
+ user_credits_db[str_chat_id][credit_type] -= 1
865
+ save_db(user_credits_db)
866
+ else:
867
+ await send_with_keyboard(client, chat_id, "❌ فایل پردازش شد اما امکان ارسال در روبیکا فراهم نشد.", True)
868
+
869
+ if os.path.exists(file_name_mp3): os.remove(file_name_mp3)
870
+
871
+ async def process_legacy_vc_job(client, chat_id, src_bytes, model_url, pitch, model_name):
872
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
873
+ creds = get_user_credits(str_chat_id)
874
+
875
+ proc_msg = await send_with_keyboard(client, chat_id, f"⏳ در حال آماده‌سازی فایل‌ها...\n(مدل: {model_name})", False)
876
+
877
+ async with aiohttp.ClientSession() as session:
878
+ form = aiohttp.FormData()
879
+ form.add_field('audio_file', src_bytes, filename='input.wav', content_type='audio/wav')
880
+ form.add_field('model_url', model_url)
881
+ form.add_field('pitch', str(pitch))
882
+ form.add_field('algo', 'rmvpe+')
883
+ form.add_field('index_inf', '0.75')
884
+ form.add_field('res_filter', '3')
885
+ form.add_field('env_ratio', '0.25')
886
+ form.add_field('protect', '0.33')
887
+ form.add_field('denoise', 'false')
888
+ form.add_field('reverb', 'false')
889
+
890
+ try:
891
+ async with session.post(f"{LEGACY_BASE_URL}/upload", data=form, timeout=43200) as resp:
892
+ if resp.status != 200:
893
+ return await send_with_keyboard(client, chat_id, f"❌ خطا در ارسال فایل‌ها (کد {resp.status})", True)
894
+ data = await resp.json()
895
+ job_id = data.get("job_id")
896
+ except Exception as e:
897
+ return await send_with_keyboard(client, chat_id, f"❌ خطا در اتصال به سرور:\n{str(e)[:100]}", True)
898
+
899
+ await send_with_keyboard(client, chat_id, "✅ فایل ارسال شد. در حال پردازش و تغییر صدای شما...\n(لطفا چند دقیقه صبور باشید)", False)
900
+
901
+ final_filename = None
902
+ for _ in range(10000):
903
+ await asyncio.sleep(5)
904
+ try:
905
+ async with session.get(f"{LEGACY_BASE_URL}/status/{job_id}", timeout=20) as c_resp:
906
+ if c_resp.status == 200:
907
+ c_data = await c_resp.json()
908
+ if c_data.get("status") == "completed":
909
+ final_filename = c_data.get("filename")
910
+ break
911
+ elif c_data.get("status") in["failed", "error", "not_found"]:
912
+ return await send_with_keyboard(client, chat_id, "❌ خطای سرور در حین پردازش.", True)
913
+ except Exception:
914
+ pass
915
+
916
+ if not final_filename:
917
+ return await send_with_keyboard(client, chat_id, "❌ پردازش بیش از حد طول کشید و متوقف شد.", True)
918
+
919
+ download_url = f"{LEGACY_BASE_URL}/download/{final_filename}"
920
+ await send_with_keyboard(client, chat_id, "📥 پردازش تمام شد! در حال دانلود نتیجه...", False)
921
+ try:
922
+ async with session.get(download_url, timeout=43200) as d_resp:
923
+ if d_resp.status == 200:
924
+ result_bytes = await d_resp.read()
925
+ else:
926
+ return await send_with_keyboard(client, chat_id, "❌ خطا در دریافت فایل نهایی از سرور.", True)
927
+ except Exception:
928
+ return await send_with_keyboard(client, chat_id, "❌ خطا در اتصال هنگام دانلود خروجی.", True)
929
+
930
+ file_name_mp3 = f"vc_legacy_{uuid.uuid4().hex[:6]}.mp3"
931
+ await asyncio.to_thread(sync_write_file, file_name_mp3, result_bytes)
932
+
933
+ upload_result = False
934
+ for up_att in range(3):
935
+ res = await helper_upload_file(client, chat_id, file_name_mp3, "Music", f"🎙️ صدای خروجی با مدل {model_name} آماده است!")
936
+ if res is True:
937
+ upload_result = True
938
+ break
939
+ await asyncio.sleep(4)
940
+
941
+ if upload_result is True:
942
+ if not creds.get("is_premium"):
943
+ user_credits_db[str_chat_id]["voice_conv"] -= 1
944
+ save_db(user_credits_db)
945
+ else:
946
+ await send_with_keyboard(client, chat_id, "❌ فایل پردازش شد اما امکان ارسال در روبیکا فراهم نشد.", True)
947
+
948
+ if os.path.exists(file_name_mp3): os.remove(file_name_mp3)
949
+
950
+ # ==============================================================================
951
+ # 🟢 پارت 13: پردازش هوش مصنوعی متنی و تصویری جیمینای و هاگینگ‌فیس
952
+ # ==============================================================================
953
+ # ==================================================================
954
+ # سایر توابع پردازشی
955
+ # ==================================================================
956
+ async def process_gemini(client, chat_id, prompt, file_bytes=None, file_name=None):
957
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
958
+ creds = get_user_credits(str_chat_id)
959
+ if creds["chat"] <= 0:
960
+ return await send_with_keyboard(client, chat_id, "❌ اعتبار پیام‌های چت شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False)
961
+
962
+ if not GEMINI_KEYS: return await send_with_keyboard(client, chat_id, "❌ کلیدهای API جیمینای تنظیم نشده‌اند.", False)
963
+
964
+ proc_msg = await send_with_keyboard(client, chat_id, "🧠 در حال پردازش...", False)
965
+ history = user_states[chat_id].get("history",[])
966
+ new_parts =[]
967
+
968
+ is_image = True
969
+ has_non_image_file = False
970
+
971
+ if prompt: new_parts.append({"text": prompt})
972
+ elif file_bytes: new_parts.append({"text": "لطفاً این فایل را به دقت بررسی کن."})
973
+
974
+ if file_bytes and file_name:
975
+ base64_data = base64.b64encode(file_bytes).decode('utf-8')
976
+ mime_type, _ = mimetypes.guess_type(file_name)
977
+ if not mime_type:
978
+ if file_name.endswith(('.jpg', '.jpeg')): mime_type = "image/jpeg"
979
+ elif file_name.endswith('.png'): mime_type = "image/png"
980
+ elif file_name.endswith('.pdf'): mime_type = "application/pdf"
981
+ elif file_name.endswith('.mp4'): mime_type = "video/mp4"
982
+ elif file_name.endswith('.mp3'): mime_type = "audio/mp3"
983
+ elif file_name.endswith(('.ogg', '.oga')): mime_type = "audio/ogg"
984
+ elif file_name.endswith('.wav'): mime_type = "audio/wav"
985
+ else: mime_type = "image/jpeg"
986
+
987
+ is_image = mime_type.startswith('image/')
988
+ if not is_image:
989
+ has_non_image_file = True
990
+
991
+ new_parts.append({"inlineData": {"mimeType": mime_type, "data": base64_data}})
992
+
993
+ if history and history[-1]["role"] == "user": history[-1]["parts"].extend(new_parts)
994
+ else: history.append({"role": "user", "parts": new_parts})
995
+
996
+ if len(history) > 40:
997
+ history = history[-40:]
998
+ if history[0]["role"] == "model": history = history[1:]
999
+
1000
+ final_answer = None
1001
+ for attempt in range(3):
1002
+ keys_to_try = get_next_gemini_keys(100)
1003
+ async with aiohttp.ClientSession() as session:
1004
+ for key in keys_to_try:
1005
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={key}"
1006
+ payload = {"contents": history, "generationConfig": {"temperature": 0.7, "maxOutputTokens": 8192}}
1007
+ try:
1008
+ async with session.post(url, json=payload, timeout=60) as response:
1009
+ if response.status == 200:
1010
+ data = await response.json()
1011
+ try:
1012
+ final_answer = data["candidates"][0]["content"]["parts"][0]["text"]
1013
+ break
1014
+ except (KeyError, IndexError): continue
1015
+ except Exception: continue
1016
+ if final_answer: break
1017
+ await asyncio.sleep(2)
1018
+
1019
+ if not final_answer:
1020
+ if has_non_image_file:
1021
+ if history and history[-1]["role"] == "user": history.pop()
1022
+ try:
1023
+ if proc_msg:
1024
+ msg_id = getattr(proc_msg, 'message_id', None)
1025
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1026
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1027
+ except Exception: pass
1028
+ await send_with_keyboard(client, chat_id, "❌ متأسفانه سرور اصلی در حال حاضر شلوغ است و در این شرایط **فعلاً تنها امکان تحلیل تصاویر** توسط سرور جایگزین در دسترس است.", False)
1029
+ return
1030
+
1031
+ if HF_TOKENS:
1032
+ hf_messages =[]
1033
+ for msg in history:
1034
+ role = "assistant" if msg["role"] == "model" else "user"
1035
+ content_parts =[]
1036
+ for part in msg["parts"]:
1037
+ if "text" in part:
1038
+ content_parts.append({"type": "text", "text": part["text"]})
1039
+ elif "inlineData" in part:
1040
+ mime_t = part["inlineData"]["mimeType"]
1041
+ b64_d = part["inlineData"]["data"]
1042
+ if mime_t.startswith('image/'):
1043
+ content_parts.append({
1044
+ "type": "image_url",
1045
+ "image_url": {"url": f"data:{mime_t};base64,{b64_d}"}
1046
+ })
1047
+ if content_parts:
1048
+ hf_messages.append({"role": role, "content": content_parts})
1049
+
1050
+ for attempt in range(3):
1051
+ keys_to_try_hf = HF_TOKENS.copy()
1052
+ random.shuffle(keys_to_try_hf)
1053
+ async with aiohttp.ClientSession() as session:
1054
+ for hf_key in keys_to_try_hf:
1055
+ url = "https://router.huggingface.co/v1/chat/completions"
1056
+ headers = {"Authorization": f"Bearer {hf_key}", "Content-Type": "application/json"}
1057
+ payload = {
1058
+ "model": "google/gemma-4-31B-it:novita",
1059
+ "messages": hf_messages,
1060
+ "max_tokens": 4096
1061
+ }
1062
+ try:
1063
+ async with session.post(url, headers=headers, json=payload, timeout=60) as response:
1064
+ if response.status == 200:
1065
+ data = await response.json()
1066
+ final_answer = data["choices"][0]["message"]["content"]
1067
+ break
1068
+ except Exception:
1069
+ continue
1070
+ if final_answer: break
1071
+ await asyncio.sleep(2)
1072
+
1073
+ try:
1074
+ if proc_msg:
1075
+ msg_id = getattr(proc_msg, 'message_id', None)
1076
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1077
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1078
+ except Exception: pass
1079
+
1080
+ if not final_answer:
1081
+ if history and history[-1]["role"] == "user": history.pop()
1082
+ await send_with_keyboard(client, chat_id, "❌ تمامی سرورها شلوغ هستند. لطفاً بعداً امتحان کنید.", False)
1083
+ return
1084
+
1085
+ history.append({"role": "model", "parts":[{"text": final_answer}]})
1086
+ user_states[chat_id]["history"] = history
1087
+
1088
+ try:
1089
+ max_len = 1000
1090
+ chunks =[]
1091
+ temp_text = final_answer
1092
+ while len(temp_text) > max_len:
1093
+ split_idx = temp_text.rfind('\n', 0, max_len)
1094
+ if split_idx == -1: split_idx = temp_text.rfind(' ', 0, max_len)
1095
+ if split_idx == -1: split_idx = max_len
1096
+ chunks.append(temp_text[:split_idx])
1097
+ temp_text = temp_text[split_idx:].strip()
1098
+ if temp_text: chunks.append(temp_text)
1099
+
1100
+ success_sent = False
1101
+ for idx, chunk in enumerate(chunks):
1102
+ if idx != len(chunks) - 1: chunk += "\n\n⏳ *(ادامه در پیام بعدی)...* 👇"
1103
+ try:
1104
+ res = await send_with_keyboard(client, chat_id, chunk, False)
1105
+ if res: success_sent = True
1106
+ await asyncio.sleep(2.5)
1107
+ except Exception: await asyncio.sleep(2.5)
1108
+
1109
+ if success_sent and not creds.get("is_premium"):
1110
+ user_credits_db[str_chat_id]["chat"] -= 1
1111
+ save_db(user_credits_db)
1112
+
1113
+ except Exception:
1114
+ await send_with_keyboard(client, chat_id, "❌ خطایی در ارسال پیام رخ داد.", False)
1115
+
1116
+ # ==============================================================================
1117
+ # 🟢 پارت 14: ساخت عکس با هوش مصنوعی
1118
+ # ==============================================================================
1119
+ async def process_image(client, chat_id, prompt, size_choice="1"):
1120
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
1121
+ creds = get_user_credits(str_chat_id)
1122
+ if creds["image"] <= 0:
1123
+ return await send_with_keyboard(client, chat_id, "❌ اعتبار ساخت عکس شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False)
1124
+
1125
+ if not HF_TOKENS: return await send_with_keyboard(client, chat_id, "❌ توکن‌های هاگینگ فیس تنظیم نشده‌اند.", False)
1126
+
1127
+ proc_msg = await send_with_keyboard(client, chat_id, "✨ در حال ترجمه و بهینه‌سازی پرامپت شما...\n(تبدیل به پرامپت حرفه‌ای)", False)
1128
+ enhanced_prompt = prompt
1129
+ gemini_sys_prompt = f"You are an expert AI image generation prompt engineer. Translate the following user input to English, and enhance it with high-quality, highly detailed, 4k resolution, cinematic lighting, and visually striking descriptive keywords. Return ONLY the final English prompt string.\nUser input: {prompt}"
1130
+
1131
+ if GEMINI_KEYS:
1132
+ keys_to_try_gemini = get_next_gemini_keys(100)
1133
+ async with aiohttp.ClientSession() as session:
1134
+ for key in keys_to_try_gemini:
1135
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={key}"
1136
+ payload = {"contents": [{"parts":[{"text": gemini_sys_prompt}]}], "generationConfig": {"temperature": 0.7}}
1137
+ try:
1138
+ async with session.post(url, json=payload, timeout=20) as response:
1139
+ if response.status == 200:
1140
+ data = await response.json()
1141
+ enhanced_prompt = data["candidates"][0]["content"]["parts"][0]["text"].strip()
1142
+ break
1143
+ except Exception: continue
1144
+
1145
+ if enhanced_prompt == prompt and HF_TOKENS:
1146
+ keys_to_try_hf = HF_TOKENS.copy()
1147
+ random.shuffle(keys_to_try_hf)
1148
+ async with aiohttp.ClientSession() as session:
1149
+ for hf_key in keys_to_try_hf:
1150
+ url = "https://router.huggingface.co/v1/chat/completions"
1151
+ headers = {"Authorization": f"Bearer {hf_key}", "Content-Type": "application/json"}
1152
+ payload = {
1153
+ "model": "google/gemma-4-31B-it:novita",
1154
+ "messages":[{"role": "user", "content":[{"type": "text", "text": gemini_sys_prompt}]}],
1155
+ "max_tokens": 1024
1156
+ }
1157
+ try:
1158
+ async with session.post(url, headers=headers, json=payload, timeout=20) as response:
1159
+ if response.status == 200:
1160
+ data = await response.json()
1161
+ enhanced_prompt = data["choices"][0]["message"]["content"].strip()
1162
+ break
1163
+ except Exception:
1164
+ continue
1165
+
1166
+ try:
1167
+ if proc_msg:
1168
+ msg_id = getattr(proc_msg, 'message_id', None)
1169
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1170
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1171
+ except Exception: pass
1172
+
1173
+ w, h = 1024, 1024
1174
+ size_name = "مربع (1:1) ⬛"
1175
+ if size_choice == "2":
1176
+ w, h = 768, 1344
1177
+ size_name = "عمودی (9:16) 📱"
1178
+ elif size_choice == "3":
1179
+ w, h = 1344, 768
1180
+ size_name = "افقی (16:9) 🖥️"
1181
+ elif size_choice == "4":
1182
+ w, h = 1024, 768
1183
+ size_name = "استاندارد (4:3) 📸"
1184
+
1185
+ short_preview = enhanced_prompt[:150] + "..." if len(enhanced_prompt) > 150 else enhanced_prompt
1186
+ proc_msg = await send_with_keyboard(client, chat_id, f"🎨 در حال طراحی عکس...\n\n📏 **ابعاد:** {size_name}\n📝 **پرامپت ساخته شده:**\n`{short_preview}`\n\n(ممکن است چند ثانیه زمان ببرد)", False)
1187
+
1188
+ generated_image = None
1189
+ last_error_log = "هیچ اتصالی برقرار نشد."
1190
+
1191
+ for attempt in range(5):
1192
+ keys_to_try = HF_TOKENS.copy()
1193
+ random.shuffle(keys_to_try)
1194
+ for token in keys_to_try:
1195
+ try:
1196
+ hf_client = AsyncInferenceClient(provider="fal-ai", api_key=token)
1197
+ generated_image = await hf_client.text_to_image(
1198
+ enhanced_prompt,
1199
+ model="Tongyi-MAI/Z-Image-Turbo",
1200
+ width=w,
1201
+ height=h
1202
+ )
1203
+ break
1204
+ except Exception as e:
1205
+ last_error_log = str(e)
1206
+ continue
1207
+ if generated_image: break
1208
+ await asyncio.sleep(2)
1209
+
1210
+ try:
1211
+ if proc_msg:
1212
+ msg_id = getattr(proc_msg, 'message_id', None)
1213
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1214
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1215
+ except Exception: pass
1216
+
1217
+ if not generated_image:
1218
+ return await send_with_keyboard(client, chat_id, f"❌ عکس ساخته نشد.\n\n⚠️ خطا:\n{last_error_log[:200]}", True)
1219
+
1220
+ try:
1221
+ file_name = f"image_{uuid.uuid4().hex}.jpg"
1222
+ await asyncio.to_thread(sync_save_image, generated_image, file_name)
1223
+ await asyncio.sleep(1)
1224
+ caption_text = f"🎨 تصویر شما با پرامپت هوشمند آماده شد!\n\n📏 ابعاد تصویر: {size_name}\n✨ ایده اولیه: {prompt}"
1225
+
1226
+ upload_result = False
1227
+ error_log_img = ""
1228
+ for up_att in range(3):
1229
+ res = await helper_upload_file(client, chat_id, file_name, "Image", caption_text)
1230
+ if res is True:
1231
+ upload_result = True
1232
+ break
1233
+ else:
1234
+ error_log_img = res
1235
+ await asyncio.sleep(4)
1236
+
1237
+ if upload_result is True:
1238
+ if not creds.get("is_premium"):
1239
+ user_credits_db[str_chat_id]["image"] -= 1
1240
+ save_db(user_credits_db)
1241
+ else:
1242
+ await send_with_keyboard(client, chat_id, f"❌ عکس ساخته شد اما آپلود در روبیکا با خطا مواجه شد:\n`{str(error_log_img)[:800]}`", True)
1243
+
1244
+ if os.path.exists(file_name): os.remove(file_name)
1245
+ except Exception as e:
1246
+ await send_with_keyboard(client, chat_id, f"❌ خطا در ذخیره و ارسال عکس:\n{str(e)[:150]}", True)
1247
+
1248
+ # ==============================================================================
1249
+ # 🟢 پارت 15: توابع ترجمه و ویرایش عکس با هوش مصنوعی (Flux.2)
1250
+ # ==============================================================================
1251
+ async def translate_text_aloha(prompt_text):
1252
+ session_hash = ''.join(random.choices(string.ascii_lowercase + string.digits, k=11))
1253
+ join_url = "https://hamed744-translate-tts-aloha.hf.space/gradio_api/queue/join"
1254
+ payload = {
1255
+ "data":[prompt_text, "انگلیسی (آمریکا) - جنی (زن)", 0, 0, 0],
1256
+ "fn_index": 1,
1257
+ "session_hash": session_hash
1258
+ }
1259
+
1260
+ try:
1261
+ async with aiohttp.ClientSession() as session:
1262
+ async with session.post(join_url, json=payload, timeout=20) as resp:
1263
+ if resp.status != 200:
1264
+ return prompt_text
1265
+
1266
+ data_url = f"https://hamed744-translate-tts-aloha.hf.space/gradio_api/queue/data?session_hash={session_hash}"
1267
+ async with session.get(data_url, timeout=60) as resp:
1268
+ async for line_bytes in resp.content:
1269
+ line = line_bytes.decode('utf-8').strip()
1270
+ if line.startswith("data: "):
1271
+ try:
1272
+ json_data = json.loads(line[6:])
1273
+ if json_data.get("msg") == "process_completed":
1274
+ if json_data.get("success"):
1275
+ return json_data["output"]["data"][0]
1276
+ break
1277
+ except Exception:
1278
+ pass
1279
+ except Exception:
1280
+ pass
1281
+
1282
+ return prompt_text
1283
+
1284
+ async def process_image_edit(client, chat_id, image_bytes, prompt):
1285
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
1286
+ creds = get_user_credits(str_chat_id)
1287
+ if creds["edit_image"] <= 0:
1288
+ return await send_with_keyboard(client, chat_id, "❌ اعتبار ویرایش عکس شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False)
1289
+
1290
+ if not HF_TOKENS:
1291
+ return await send_with_keyboard(client, chat_id, "❌ توکن‌های هاگینگ فیس تنظیم نشده‌اند.", False)
1292
+
1293
+ proc_msg = await send_with_keyboard(client, chat_id, "🪄 در حال ترجمه دستور شما توسط اسپیس و اعمال جادوی FLUX.2...\n(این فرآیند ممکن است کمی طول بکشد)", False)
1294
+
1295
+ translated_prompt = await translate_text_aloha(prompt)
1296
+ if not translated_prompt or translated_prompt.strip() == "":
1297
+ translated_prompt = prompt
1298
+
1299
+ generated_image = None
1300
+ last_error_log = "سرور پاسخ نداد."
1301
+
1302
+ for attempt in range(5):
1303
+ keys_to_try = HF_TOKENS.copy()
1304
+ random.shuffle(keys_to_try)
1305
+ for token in keys_to_try:
1306
+ try:
1307
+ hf_client = AsyncInferenceClient(provider="fal-ai", api_key=token)
1308
+ generated_image = await hf_client.image_to_image(
1309
+ image_bytes,
1310
+ prompt=translated_prompt,
1311
+ model="black-forest-labs/FLUX.2-dev"
1312
+ )
1313
+ break
1314
+ except Exception as e:
1315
+ last_error_log = str(e)
1316
+ continue
1317
+ if generated_image: break
1318
+ await asyncio.sleep(2)
1319
+
1320
+ try:
1321
+ if proc_msg:
1322
+ msg_id = getattr(proc_msg, 'message_id', None)
1323
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1324
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1325
+ except Exception: pass
1326
+
1327
+ if not generated_image:
1328
+ return await send_with_keyboard(client, chat_id, f"❌ متأسفانه ویرایش عکس انجام نشد.\n\n⚠️ علت:\n{last_error_log[:200]}", True)
1329
+
1330
+ try:
1331
+ file_name = f"edited_flux_{uuid.uuid4().hex}.jpg"
1332
+ await asyncio.to_thread(sync_save_image, generated_image, file_name)
1333
+ await asyncio.sleep(1)
1334
+ caption_text = f"🪄 ویرایش عکس با هوش مصنوعی Flux.2 انجام شد!\n\n✨ تغییرات خواسته شده: {prompt}\n🔤 متن ارسال شده به هوش: {translated_prompt}"
1335
+
1336
+ upload_result = False
1337
+ error_log_edit = ""
1338
+ for up_att in range(3):
1339
+ res = await helper_upload_file(client, chat_id, file_name, "Image", caption_text)
1340
+ if res is True:
1341
+ upload_result = True
1342
+ break
1343
+ else:
1344
+ error_log_edit = res
1345
+ await asyncio.sleep(4)
1346
+
1347
+ if upload_result is True:
1348
+ if not creds.get("is_premium"):
1349
+ user_credits_db[str_chat_id]["edit_image"] -= 1
1350
+ save_db(user_credits_db)
1351
+ else:
1352
+ await send_with_keyboard(client, chat_id, f"❌ عکس ساخته شد اما خطا در ارسال به روبیکا رخ داد:\n`{str(error_log_edit)[:800]}`", True)
1353
+
1354
+ if os.path.exists(file_name): os.remove(file_name)
1355
+ except Exception as e:
1356
+ await send_with_keyboard(client, chat_id, f"❌ خطا در ذخیره عکس ویرایش شده:\n{str(e)[:150]}", True)
1357
+
1358
+ # ==============================================================================
1359
+ # 🟢 پارت 16: ساخت صدا از روی متن (تبدیل متن به صدا - TTS)
1360
+ # ==============================================================================
1361
+ async def process_tts(client, chat_id, user_text, speaker_id, speaker_name):
1362
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
1363
+ creds = get_user_credits(str_chat_id)
1364
+ if creds["tts"] <= 0:
1365
+ return await send_with_keyboard(client, chat_id, "❌ اعتبار تبدیل متن به صدای شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False)
1366
+
1367
+ try:
1368
+ proc_msg = await send_with_keyboard(client, chat_id, f"⏳ در حال ساخت صدا با «{speaker_name}»...\n(لطفاً صبور باشید)", False)
1369
+ payload = {"text": user_text, "speaker": speaker_id, "temperature": 1.5, "prompt": "", "use_live_model": True}
1370
+ headers = {"User-Agent": "Mozilla/5.0", "Content-Type": "application/json"}
1371
+
1372
+ audio_bytes = None
1373
+ last_error = "پاسخی دریافت نشد"
1374
+
1375
+ for attempt in range(10):
1376
+ workers = WORKER_URLS.copy()
1377
+ random.shuffle(workers)
1378
+ async with aiohttp.ClientSession(headers=headers, timeout=aiohttp.ClientTimeout(total=600)) as session:
1379
+ for worker_url in workers[:3]:
1380
+ try:
1381
+ async with session.post(worker_url, json=payload) as response:
1382
+ if response.status == 200:
1383
+ content_type = response.headers.get('Content-Type', '')
1384
+ if 'audio' in content_type or response.content_length > 1000:
1385
+ audio_bytes = await response.read()
1386
+ break
1387
+ else: last_error = "فایل نامعتبر"
1388
+ else: last_error = f"ارور ({response.status})"
1389
+ except Exception as e:
1390
+ last_error = f"خطا: {str(e)}"
1391
+ continue
1392
+ if audio_bytes: break
1393
+ await asyncio.sleep(2)
1394
+
1395
+ try:
1396
+ if proc_msg:
1397
+ msg_id = getattr(proc_msg, 'message_id', None)
1398
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1399
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1400
+ except Exception: pass
1401
+
1402
+ if audio_bytes:
1403
+ file_name_mp3 = f"audio_{uuid.uuid4().hex}.mp3"
1404
+ await asyncio.to_thread(sync_write_file, file_name_mp3, audio_bytes)
1405
+ await asyncio.sleep(1)
1406
+
1407
+ upload_result_file = False
1408
+ error_log_tts = ""
1409
+ for up_att in range(3):
1410
+ res = await helper_upload_file(client, chat_id, file_name_mp3, "Music", "✅ صدای شما با موفقیت آماده شد (فایل MP3):")
1411
+ if res is True:
1412
+ upload_result_file = True
1413
+ break
1414
+ else:
1415
+ error_log_tts = res
1416
+ await asyncio.sleep(4)
1417
+
1418
+ if upload_result_file is True:
1419
+ if not creds.get("is_premium"):
1420
+ user_credits_db[str_chat_id]["tts"] -= 1
1421
+ save_db(user_credits_db)
1422
+ else:
1423
+ await send_with_keyboard(client, chat_id, f"❌ فایل صدا ساخته شد اما سرور روبیکا اجازه آپلود نداد:\n`{str(error_log_tts)[:800]}`", True)
1424
+
1425
+ if os.path.exists(file_name_mp3): os.remove(file_name_mp3)
1426
+ else:
1427
+ await send_with_keyboard(client, chat_id, f"❌ سرورها درگیر هستند.\nدلیل: {last_error}", True)
1428
+ except Exception: traceback.print_exc()
1429
+
1430
+ # ==============================================================================
1431
+ # 🟢 پارت 17: سیستم ساخت پادکست
1432
+ # ==============================================================================
1433
+ async def process_podcast(client, chat_id, prompt):
1434
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
1435
+ creds = get_user_credits(str_chat_id)
1436
+ if creds["podcast"] <= 0:
1437
+ return await send_with_keyboard(client, chat_id, "❌ اعتبار ساخت پادکست شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False)
1438
+
1439
+ proc_msg = await send_with_keyboard(client, chat_id, "📻 در حال بررسی موضوع و نگارش سناریوی پادکست توسط هوش مصنوعی...\n(لطفاً صبور باشید)", False)
1440
+ available_speakers =[]
1441
+ for num_key, (spk_name, spk_id) in SPEAKERS.items():
1442
+ gender = "male" if "مرد" in spk_name else "female"
1443
+ available_speakers.append({"id": spk_id, "name": spk_name.split(' (')[0], "gender": gender})
1444
+
1445
+ url_create = "https://opera8-podgen.hf.space/api/create-full-podcast"
1446
+ payload_create = {"prompt": prompt, "available_speakers": available_speakers}
1447
+
1448
+ async with aiohttp.ClientSession() as session:
1449
+ try:
1450
+ async with session.post(url_create, json=payload_create, timeout=60) as resp:
1451
+ if resp.status != 202: return await send_with_keyboard(client, chat_id, f"❌ خطا در سرور پادکست: وضعیت {resp.status}", True)
1452
+ task_id = (await resp.json()).get("task_id")
1453
+ except Exception as e: return await send_with_keyboard(client, chat_id, f"❌ خطا در اتصال به سرور پادکست:\n{str(e)}", True)
1454
+
1455
+ url_status = f"https://opera8-podgen.hf.space/api/podcast-status/{task_id}"
1456
+ script_data = None
1457
+ for _ in range(500):
1458
+ await asyncio.sleep(3)
1459
+ try:
1460
+ async with session.get(url_status) as resp:
1461
+ if resp.status == 200:
1462
+ status_data = await resp.json()
1463
+ if status_data.get("status") == "completed":
1464
+ script_data = status_data.get("data", {}).get("script",[])
1465
+ break
1466
+ elif status_data.get("status") == "failed":
1467
+ return await send_with_keyboard(client, chat_id, "❌ متأسفانه هوش مصنوعی نتوانست برای این موضوع سناریو بنویسد.", True)
1468
+ except Exception: pass
1469
+
1470
+ if not script_data: return await send_with_keyboard(client, chat_id, "❌ زمان انتظار برای نوشتن سناریو به پایان رسید.", True)
1471
+
1472
+ try:
1473
+ if proc_msg:
1474
+ msg_id = getattr(proc_msg, 'message_id', None)
1475
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1476
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1477
+ except: pass
1478
+
1479
+ proc_msg = await send_with_keyboard(client, chat_id, f"🎙 سناریو نوشته شد! در حال ریکورد و م��کس دیالوگ‌های گویندگان (شامل {len(script_data)} نوبت صحبت)...\nاین مرحله زمان‌بر است...", False)
1480
+
1481
+ combined_audio = AudioSegment.empty()
1482
+ url_generate = "https://opera8-podgen.hf.space/api/generate"
1483
+
1484
+ for index, turn in enumerate(script_data):
1485
+ payload_gen = {"text": turn["dialogue"], "speaker": turn["speaker_id"], "temperature": 0.9}
1486
+ chunk_audio_bytes = None
1487
+
1488
+ for attempt in range(10):
1489
+ try:
1490
+ async with session.post(url_generate, json=payload_gen, timeout=120) as resp:
1491
+ if resp.status == 200:
1492
+ chunk_audio_bytes = await resp.read()
1493
+ break
1494
+ except Exception: await asyncio.sleep(2)
1495
+
1496
+ if not chunk_audio_bytes:
1497
+ return await send_with_keyboard(client, chat_id, f"❌ خطا در تولید صدای بخش {index+1} از سرور. عملیات متوقف شد.", True)
1498
+
1499
+ try:
1500
+ combined_audio = await asyncio.to_thread(sync_combine_audio, combined_audio, chunk_audio_bytes)
1501
+ except Exception as e:
1502
+ return await send_with_keyboard(client, chat_id, f"❌ خطا در پردازش صدا:\n{str(e)}", True)
1503
+
1504
+ file_name_mp3 = f"final_podcast_{uuid.uuid4().hex}.mp3"
1505
+ await asyncio.to_thread(sync_export_audio, combined_audio, file_name_mp3)
1506
+
1507
+ try:
1508
+ if proc_msg:
1509
+ msg_id = getattr(proc_msg, 'message_id', None)
1510
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1511
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1512
+ except: pass
1513
+
1514
+ caption_file = f"🎧 فایل پادکست شما با فرمت MP3:\n\n💡 موضوع شما: {prompt}"
1515
+
1516
+ upload_result_file = False
1517
+ error_log_pod = ""
1518
+ for up_att in range(3):
1519
+ res = await helper_upload_file(client, chat_id, file_name_mp3, "Music", caption_file)
1520
+ if res is True:
1521
+ upload_result_file = True
1522
+ break
1523
+ else:
1524
+ error_log_pod = res
1525
+ await asyncio.sleep(5)
1526
+
1527
+ if upload_result_file is True:
1528
+ if not creds.get("is_premium"):
1529
+ user_credits_db[str_chat_id]["podcast"] -= 1
1530
+ save_db(user_credits_db)
1531
+ else:
1532
+ await send_with_keyboard(client, chat_id, f"❌ پادکست ساخته شد اما روبیکا پس از ده‌ها تلاش خطای آپلود داد.\n\n`{str(error_log_pod)[:800]}`", True)
1533
+
1534
+ if os.path.exists(file_name_mp3): os.remove(file_name_mp3)
1535
+
1536
+ # ==============================================================================
1537
+ # 🟢 پارت 18: استخراج متن از صدا (STT) و تحلیل انواع فایل با هوش مصنوعی
1538
+ # ==============================================================================
1539
+ async def process_stt(client, chat_id, audio_bytes, file_name):
1540
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
1541
+ creds = get_user_credits(str_chat_id)
1542
+ if creds["stt"] <= 0:
1543
+ return await send_with_keyboard(client, chat_id, "❌ اعتبار تبدیل صدا به متن شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False)
1544
+
1545
+ if not GEMINI_KEYS: return await send_with_keyboard(client, chat_id, "❌ کلیدهای جیمینای تنظیم نشده‌اند.", False)
1546
+
1547
+ proc_msg = await send_with_keyboard(client, chat_id, "📝 در حال گوش دادن و پیاده‌سازی متن...", False)
1548
+ base64_data = base64.b64encode(audio_bytes).decode('utf-8')
1549
+ mime_type, _ = mimetypes.guess_type(file_name)
1550
+ if not mime_type: mime_type = "audio/ogg"
1551
+
1552
+ prompt = "لطفاً این فایل صوتی/تصویری را با دقت کامل گوش بده و صحبت‌های داخل آن را کلمه به کلمه به متن تبدیل کن. هیچ توضیح اضافه‌ای نده."
1553
+
1554
+ transcribed_text = None
1555
+ for attempt in range(5):
1556
+ keys_to_try = get_next_gemini_keys(100)
1557
+ async with aiohttp.ClientSession() as session:
1558
+ for key in keys_to_try:
1559
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={key}"
1560
+ payload = {"contents":[{"parts":[{"text": prompt}, {"inlineData": {"mimeType": mime_type, "data": base64_data}}]}], "generationConfig": {"temperature": 0.2}}
1561
+ try:
1562
+ async with session.post(url, json=payload, timeout=60) as response:
1563
+ if response.status == 200:
1564
+ data = await response.json()
1565
+ transcribed_text = data["candidates"][0]["content"]["parts"][0]["text"]
1566
+ break
1567
+ except Exception: continue
1568
+ if transcribed_text: break
1569
+ await asyncio.sleep(2)
1570
+
1571
+ try:
1572
+ if proc_msg:
1573
+ msg_id = getattr(proc_msg, 'message_id', None)
1574
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1575
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1576
+ except Exception: pass
1577
+
1578
+ if transcribed_text:
1579
+ sent = await send_with_keyboard(client, chat_id, f"📝 **متن استخراج شده:**\n\n{transcribed_text}", True)
1580
+ if sent and not creds.get("is_premium"):
1581
+ user_credits_db[str_chat_id]["stt"] -= 1
1582
+ save_db(user_credits_db)
1583
+ else:
1584
+ await send_with_keyboard(client, chat_id, "❌ سرور شلوغ است فعلا بعدا امتحان کنید.", True)
1585
+
1586
+ async def process_file_analysis(client, chat_id, file_bytes, file_name, prompt):
1587
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
1588
+ creds = get_user_credits(str_chat_id)
1589
+ if creds["file"] <= 0:
1590
+ return await send_with_keyboard(client, chat_id, "❌ اعتبار تحلیل فایل شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False)
1591
+
1592
+ if not GEMINI_KEYS: return await send_with_keyboard(client, chat_id, "❌ کلید جیمینای تنظیم نیست.", False)
1593
+
1594
+ proc_msg = await send_with_keyboard(client, chat_id, "👁️ در حال تحلیل فایل...", False)
1595
+ base64_data = base64.b64encode(file_bytes).decode('utf-8')
1596
+ mime_type, _ = mimetypes.guess_type(file_name)
1597
+ if not mime_type:
1598
+ if file_name.endswith(('.jpg', '.jpeg')): mime_type = "image/jpeg"
1599
+ elif file_name.endswith('.png'): mime_type = "image/png"
1600
+ elif file_name.endswith('.pdf'): mime_type = "application/pdf"
1601
+ elif file_name.endswith('.mp4'): mime_type = "video/mp4"
1602
+ elif file_name.endswith('.mp3'): mime_type = "audio/mp3"
1603
+ elif file_name.endswith(('.ogg', '.oga')): mime_type = "audio/ogg"
1604
+ elif file_name.endswith('.wav'): mime_type = "audio/wav"
1605
+ else: mime_type = "image/jpeg"
1606
+
1607
+ is_image = mime_type.startswith('image/')
1608
+
1609
+ final_answer = None
1610
+ for attempt in range(5):
1611
+ keys_to_try = get_next_gemini_keys(100)
1612
+ async with aiohttp.ClientSession() as session:
1613
+ for key in keys_to_try:
1614
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={key}"
1615
+ payload = {"contents": [{"parts":[{"text": prompt}, {"inlineData": {"mimeType": mime_type, "data": base64_data}}]}], "generationConfig": {"temperature": 0.6}}
1616
+ try:
1617
+ async with session.post(url, json=payload, timeout=45) as response:
1618
+ if response.status == 200:
1619
+ data = await response.json()
1620
+ final_answer = data["candidates"][0]["content"]["parts"][0]["text"]
1621
+ break
1622
+ except Exception: continue
1623
+ if final_answer: break
1624
+ await asyncio.sleep(2)
1625
+
1626
+ if not final_answer:
1627
+ if is_image and HF_TOKENS:
1628
+ for attempt in range(5):
1629
+ keys_to_try_hf = HF_TOKENS.copy()
1630
+ random.shuffle(keys_to_try_hf)
1631
+
1632
+ hf_messages =[
1633
+ {
1634
+ "role": "user",
1635
+ "content":[
1636
+ {"type": "text", "text": prompt},
1637
+ {
1638
+ "type": "image_url",
1639
+ "image_url": {"url": f"data:{mime_type};base64,{base64_data}"}
1640
+ }
1641
+ ]
1642
+ }
1643
+ ]
1644
+
1645
+ async with aiohttp.ClientSession() as session:
1646
+ for hf_key in keys_to_try_hf:
1647
+ url = "https://router.huggingface.co/v1/chat/completions"
1648
+ headers = {"Authorization": f"Bearer {hf_key}", "Content-Type": "application/json"}
1649
+ payload = {
1650
+ "model": "google/gemma-4-31B-it:novita",
1651
+ "messages": hf_messages,
1652
+ "max_tokens": 4096
1653
+ }
1654
+ try:
1655
+ async with session.post(url, headers=headers, json=payload, timeout=60) as response:
1656
+ if response.status == 200:
1657
+ data = await response.json()
1658
+ final_answer = data["choices"][0]["message"]["content"]
1659
+ break
1660
+ except Exception:
1661
+ continue
1662
+ if final_answer: break
1663
+ await asyncio.sleep(2)
1664
+
1665
+ try:
1666
+ if proc_msg:
1667
+ msg_id = getattr(proc_msg, 'message_id', None)
1668
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1669
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1670
+ except Exception: pass
1671
+
1672
+ if final_answer:
1673
+ sent = await send_with_keyboard(client, chat_id, f"💡 **نتیجه تحلیل:**\n\n{final_answer}", True)
1674
+ if sent and not creds.get("is_premium"):
1675
+ user_credits_db[str_chat_id]["file"] -= 1
1676
+ save_db(user_credits_db)
1677
+ else:
1678
+ if not is_image:
1679
+ await send_with_keyboard(client, chat_id, "❌ متأسفانه سرور اصلی در حال حاضر شلوغ است و در این شرایط **فعلاً تنها امکان تحلیل تصاویر** توسط سرور جایگزین در دسترس است.", True)
1680
+ else:
1681
+ await send_with_keyboard(client, chat_id, "❌ تمامی سرورها شلوغ هستند. لطفاً بعداً امتحان کنید.", True)
1682
+
1683
+ # ==============================================================================
1684
+ # 🟢 پارت 19: ساخت مقاله و خروجی فایل متنی (Word/PDF)
1685
+ # ==============================================================================
1686
+ async def process_create_file(client, chat_id, topic):
1687
+ str_chat_id = str(chat_id).replace("`", "").replace("'", "").replace('"', "").strip()
1688
+ creds = get_user_credits(str_chat_id)
1689
+
1690
+ if creds["chat"] <= 0:
1691
+ return await send_with_keyboard(client, chat_id, "❌ اعتبار چت شما تمام شده است. لطفاً از منوی اصلی وارد بخش «خرید اشتراک 💎» شوید.", False)
1692
+
1693
+ if not GEMINI_KEYS: return await send_with_keyboard(client, chat_id, "❌ کلیدهای جیمینای تنظیم نشده‌اند.", False)
1694
+
1695
+ proc_msg = await send_with_keyboard(client, chat_id, "✍️ در حال تحقیق و نگارش مقاله جامع و حرفه‌ای...\n(این عملیات با توجه به طولانی بودن متن ممکن است کمی طول بکشد)", False)
1696
+
1697
+ ai_prompt = f"یک مقاله بسیار جامع، کاملا حرفه‌ای، بسیار طولانی و با جزئیات کامل درباره موضوع زیر به زبان فارسی بنویس. فقط متن مقاله را بده و هیچ توضیح اضافه‌ای نده:\n\nموضوع: {topic}"
1698
+
1699
+ article_text = None
1700
+ for attempt in range(5):
1701
+ keys_to_try = get_next_gemini_keys(100)
1702
+ async with aiohttp.ClientSession() as session:
1703
+ for key in keys_to_try:
1704
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={key}"
1705
+ payload = {"contents":[{"parts":[{"text": ai_prompt}]}], "generationConfig": {"temperature": 0.7, "maxOutputTokens": 8192}}
1706
+ try:
1707
+ async with session.post(url, json=payload, timeout=60) as response:
1708
+ if response.status == 200:
1709
+ data = await response.json()
1710
+ article_text = data["candidates"][0]["content"]["parts"][0]["text"]
1711
+ break
1712
+ except Exception: continue
1713
+ if article_text: break
1714
+ await asyncio.sleep(2)
1715
+
1716
+ if not article_text and HF_TOKENS:
1717
+ for attempt in range(5):
1718
+ keys_to_try_hf = HF_TOKENS.copy()
1719
+ random.shuffle(keys_to_try_hf)
1720
+ async with aiohttp.ClientSession() as session:
1721
+ for hf_key in keys_to_try_hf:
1722
+ url = "https://router.huggingface.co/v1/chat/completions"
1723
+ headers = {"Authorization": f"Bearer {hf_key}", "Content-Type": "application/json"}
1724
+ payload = {
1725
+ "model": "google/gemma-4-31B-it:novita",
1726
+ "messages":[{"role": "user", "content":[{"type": "text", "text": ai_prompt}]}],
1727
+ "max_tokens": 4096
1728
+ }
1729
+ try:
1730
+ async with session.post(url, headers=headers, json=payload, timeout=60) as response:
1731
+ if response.status == 200:
1732
+ data = await response.json()
1733
+ article_text = data["choices"][0]["message"]["content"]
1734
+ break
1735
+ except Exception: continue
1736
+ if article_text: break
1737
+ await asyncio.sleep(2)
1738
+
1739
+ if not article_text:
1740
+ return await send_with_keyboard(client, chat_id, "❌ سرور تولید متن شلوغ است، لطفاً بعداً تلاش کنید.", True)
1741
+
1742
+ try:
1743
+ if proc_msg:
1744
+ msg_id = getattr(proc_msg, 'message_id', None)
1745
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1746
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1747
+ except Exception: pass
1748
+
1749
+ proc_msg = await send_with_keyboard(client, chat_id, "📄 مقاله با موفقیت نوشته شد! در حال ارتباط با سرور و تبدیل به فایل‌های PDF و Word...\n(لطفا صبور باشید)", False)
1750
+
1751
+ converter_url = "https://opera8-texttopdf.hf.space/"
1752
+ uid = uuid.uuid4().hex
1753
+
1754
+ pdf_success = False
1755
+ for attempt in range(30):
1756
+ try:
1757
+ pdf_bytes = None
1758
+ async with aiohttp.ClientSession() as session:
1759
+ form_data = aiohttp.FormData()
1760
+ form_data.add_field('content', article_text)
1761
+ form_data.add_field('format', 'pdf')
1762
+ async with session.post(converter_url, data=form_data, timeout=90) as resp:
1763
+ if resp.status == 200:
1764
+ data_bytes = await resp.read()
1765
+ if data_bytes and len(data_bytes) > 100:
1766
+ pdf_bytes = data_bytes
1767
+
1768
+ if pdf_bytes:
1769
+ filename = f"Article_{uid}.pdf"
1770
+ await asyncio.to_thread(sync_write_file, filename, pdf_bytes)
1771
+
1772
+ res_upload = False
1773
+ for _ in range(3):
1774
+ r = await helper_upload_file(client, chat_id, filename, "File", f"📄 فایل PDF مقاله شما:\n\n💡 موضوع: {topic}")
1775
+ if r is True:
1776
+ res_upload = True
1777
+ break
1778
+ await asyncio.sleep(4)
1779
+
1780
+ if os.path.exists(filename):
1781
+ os.remove(filename)
1782
+
1783
+ if res_upload is True:
1784
+ pdf_success = True
1785
+ break
1786
+ except Exception as e:
1787
+ print(f"PDF error (attempt {attempt+1}):", e)
1788
+
1789
+ await asyncio.sleep(4)
1790
+
1791
+ await asyncio.sleep(3.5)
1792
+
1793
+ docx_success = False
1794
+ for attempt in range(30):
1795
+ try:
1796
+ docx_bytes = None
1797
+ async with aiohttp.ClientSession() as session:
1798
+ form_data = aiohttp.FormData()
1799
+ form_data.add_field('content', article_text)
1800
+ form_data.add_field('format', 'docx')
1801
+ async with session.post(converter_url, data=form_data, timeout=90) as resp:
1802
+ if resp.status == 200:
1803
+ data_bytes = await resp.read()
1804
+ if data_bytes and len(data_bytes) > 100:
1805
+ docx_bytes = data_bytes
1806
+
1807
+ if docx_bytes:
1808
+ filename = f"Article_{uid}.docx"
1809
+ await asyncio.to_thread(sync_write_file, filename, docx_bytes)
1810
+
1811
+ res_upload = False
1812
+ for _ in range(3):
1813
+ r = await helper_upload_file(client, chat_id, filename, "File", f"📝 فایل Word (DOCX) مقاله شما:\n\n💡 موضوع: {topic}")
1814
+ if r is True:
1815
+ res_upload = True
1816
+ break
1817
+ await asyncio.sleep(4)
1818
+
1819
+ if os.path.exists(filename):
1820
+ os.remove(filename)
1821
+
1822
+ if res_upload is True:
1823
+ docx_success = True
1824
+ break
1825
+ except Exception as e:
1826
+ print(f"DOCX error (attempt {attempt+1}):", e)
1827
+
1828
+ await asyncio.sleep(4)
1829
+
1830
+ try:
1831
+ if proc_msg:
1832
+ msg_id = getattr(proc_msg, 'message_id', None)
1833
+ if isinstance(proc_msg, dict): msg_id = proc_msg.get('message_update', {}).get('message_id') or proc_msg.get('message_id')
1834
+ if msg_id: await client.delete_messages(chat_id,[msg_id])
1835
+ except Exception: pass
1836
+
1837
+ if pdf_success and docx_success:
1838
+ if not creds.get("is_premium"):
1839
+ user_credits_db[str_chat_id]["chat"] -= 1
1840
+ save_db(user_credits_db)
1841
+ await send_with_keyboard(client, chat_id, "✅ مقاله شما با موفقیت به صورت هر دو فایل (PDF و Word) تحویل داده شد!", True)
1842
+ elif pdf_success or docx_success:
1843
+ if not creds.get("is_premium"):
1844
+ user_credits_db[str_chat_id]["chat"] -= 1
1845
+ save_db(user_credits_db)
1846
+ await send_with_keyboard(client, chat_id, "⚠️ یکی از فایل‌ها با موفقیت ارسال شد اما دیگری پس از تلاش‌های مکرر سرور با خطا مواجه شد.", True)
1847
+ else:
1848
+ await send_with_keyboard(client, chat_id, "❌ متأسفانه پس از تلاش‌های مکرر، سرور قادر به ساخت و ارسال هیچ‌یک از فایل‌ها نبود و اعتباری از شما کسر نشد.", True)
1849
+
1850
+ # ==============================================================================
1851
+ # 🔴 پا��ت 20: نسخه پاکسازی اضطراری (مخصوص تخلیه صف در دیتابیس جدید v3)
1852
+ # ==============================================================================
1853
+
1854
+ if not bot_token:
1855
+ print("خطا: توکن وارد نشده!")
1856
+ else:
1857
+ bot = BotClient(bot_token)
1858
+
1859
+ @bot.on_update(filters.private)
1860
+ async def main_handler(client, update):
1861
+ try:
1862
+ # فقط استخراج آیدی پیام
1863
+ msg_obj = getattr(update, "message", None) or getattr(update, "new_message", None)
1864
+ msg_id = getattr(update, "message_id", None)
1865
+ if not msg_id and msg_obj:
1866
+ msg_id = msg_obj.get("message_id") if isinstance(msg_obj, dict) else getattr(msg_obj, "message_id", None)
1867
+
1868
+ if not msg_id: return
1869
+ str_msg_id = str(msg_id).strip()
1870
+
1871
+ # ثبت در دیتابیس v3 بدون فرستادن هیچ پاسخی
1872
+ if not is_message_processed(str_msg_id):
1873
+ mark_message_processed(str_msg_id)
1874
+ print(f"🗑️ [V3-CLEARING] پیام {str_msg_id} ثبت شد.")
1875
+
1876
+ except Exception:
1877
+ pass
1878
+
1879
+ if __name__ == "__main__":
1880
+ threading.Thread(target=run_flask, daemon=True).start()
1881
+ if bot_token:
1882
+ print("⚠️ وضعیت: در حال تخلیه صف پیام‌ها در دیتابیس v3... لطفاً تا توقف لاگ‌ها صبر کنید.")
1883
+ bot.run()