Oth1 commited on
Commit
ff13b1f
·
verified ·
1 Parent(s): 871e400

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +1185 -389
main.py CHANGED
@@ -1,427 +1,1223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import sys
3
- import time
4
  import random
5
- import logging
6
  import asyncio
7
- from datetime import datetime, timedelta
 
 
 
 
8
  from aiohttp import web
 
 
9
  from motor.motor_asyncio import AsyncIOMotorClient
10
- from aiogram import Bot, Dispatcher, Router, F
11
- from aiogram.client.default import DefaultBotProperties
12
- from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
13
- from aiogram.filters import CommandStart, Command
14
- from aiogram.fsm.context import FSMContext
15
- from aiogram.fsm.state import State, StatesGroup
16
- from aiogram.exceptions import TelegramAPIError, TelegramNetworkError
17
-
18
- # --- الإعدادات الأساسية والأمان ---
19
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
20
- logger = logging.getLogger(__name__)
21
-
22
- BOT_TOKEN = os.getenv("BOT_TOKEN")
23
- MONGO_URI = os.getenv("MONGO_URI")
24
-
25
- if not BOT_TOKEN or not MONGO_URI:
26
- logger.critical("🚨 خطأ حرج: BOT_TOKEN أو MONGO_URI غير موجود في Secrets. جاري الإيقاف.")
27
- sys.exit(1)
28
-
29
- # الملاك الأساسيون
30
- OWNERS = [6069597294, 5359398024, 7126816492]
31
- SELL_ACCOUNT = "@kk66kk6"
32
-
33
- # إعداد قاعدة البيانات مع زيادة مهلة الاستجابة
34
- client = AsyncIOMotorClient(MONGO_URI, serverSelectionTimeoutMS=15000)
35
- db = client['bounty_rush_bot']
36
-
37
- # إعداد البوت بالتوافق مع النسخ الحديثة من aiogram
38
- bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode="HTML"))
39
- dp = Dispatcher()
40
- router = Router()
41
-
42
- # --- تهيئة قاعدة البيانات والفهارس ---
43
- async def init_db():
44
- try:
45
- await db.users.create_index("user_id", unique=True)
46
- await db.products.create_index("status")
47
- await db.transactions.create_index("user_id")
48
-
49
- if not await db.settings.find_one({"_id": "bot_settings"}):
50
- await db.settings.insert_one({
51
- "_id": "bot_settings",
52
- "ref_points": 500,
53
- "msg_xp": 5,
54
- "daily_reward": 1000,
55
- "cooldown_seconds": 60,
56
- "box_price": 2000
57
- })
58
- logger.info("✅ تم الاتصال بقاعدة بيانات MongoDB وتهيئة الفهارس بنجاح.")
59
- except Exception as e:
60
- logger.error(f"⚠️ خطأ أثناء الاتصال بقاعدة البيانات: {e}")
61
-
62
- async def get_settings():
63
- settings = await db.settings.find_one({"_id": "bot_settings"})
64
- if not settings:
65
- return {
66
- "ref_points": 500,
67
- "msg_xp": 5,
68
- "daily_reward": 1000,
69
- "cooldown_seconds": 60,
70
- "box_price": 2000
71
- }
72
- return settings
73
 
74
- async def get_user(user_id: int, username: str = None):
75
- user = await db.users.find_one({"user_id": user_id})
76
- if not user:
77
- user = {
78
- "user_id": user_id,
79
- "username": username,
80
- "points": 0,
81
- "xp": 0,
82
- "level": 1,
83
- "vip": 0,
84
- "tickets": 0,
85
- "referrals": 0,
86
- "is_banned": False,
87
- "join_date": datetime.utcnow(),
88
- "last_daily": None,
89
- "last_msg_time": 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  }
91
- await db.users.insert_one(user)
92
- return user
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- async def log_transaction(user_id: int, amount: int, trans_type: str, reason: str):
95
- await db.transactions.insert_one({
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  "user_id": user_id,
97
  "amount": amount,
98
- "type": trans_type,
99
  "reason": reason,
100
- "timestamp": datetime.utcnow()
101
- })
102
-
103
- # --- لوحات المفاتيح ---
104
- def main_menu_kb():
105
- kb = [
106
- [InlineKeyboardButton(text="👤 حسابي", callback_data="my_profile"),
107
- InlineKeyboardButton(text="🛒 متجر الحسابات", callback_data="store")],
108
- [InlineKeyboardButton(text="💰 بيع حساب", callback_data="sell_acc"),
109
- InlineKeyboardButton(text="🎁 المكافأة اليومية", callback_data="daily_reward")],
110
- [InlineKeyboardButton(text="📦 صندوق الحظ", callback_data="mystery_box"),
111
- InlineKeyboardButton(text="🏆 المتصدرين", callback_data="leaderboard")],
112
- ]
113
- return InlineKeyboardMarkup(inline_keyboard=kb)
114
-
115
- def admin_menu_kb():
116
- kb = [
117
- [InlineKeyboardButton(text="🛒 إضافة منتج", callback_data="admin_add_product"),
118
- InlineKeyboardButton(text="👥 إدارة المستخدمين", callback_data="admin_users")],
119
- [InlineKeyboardButton(text="⚙️ إعدادات البوت", callback_data="admin_settings"),
120
- InlineKeyboardButton(text="📊 الإحصائيات", callback_data="admin_stats")],
121
- [InlineKeyboardButton(text="🔙 عودة للقائمة الرئيسية", callback_data="main_menu")]
122
- ]
123
- return InlineKeyboardMarkup(inline_keyboard=kb)
124
-
125
- # --- معالجة الأوامر والأحداث ---
126
- @router.message(CommandStart())
127
- async def cmd_start(message: Message):
128
- user_id = message.from_user.id
129
- args = message.text.split()
130
-
131
  user = await db.users.find_one({"user_id": user_id})
132
  if not user:
133
- await get_user(user_id, message.from_user.username)
134
- if len(args) > 1 and args[1].startswith("ref_"):
135
- try:
136
- referrer_id = int(args[1].split("_")[1])
137
- if referrer_id != user_id:
138
- settings = await get_settings()
139
- ref_points = settings.get("ref_points", 500)
140
-
141
- await db.users.update_one({"user_id": referrer_id}, {"$inc": {"points": ref_points, "referrals": 1}})
142
- await log_transaction(referrer_id, ref_points, "add", f"إحالة المستخدم {user_id}")
143
- try:
144
- await bot.send_message(referrer_id, f"🎉 لقد قام شخص جديد بالدخول من رابطك! حصلت على {ref_points} نقطة.")
145
- except: pass
146
- except: pass
147
 
148
- text = (
149
- "<b>مرحباً بك في بوت تعويضات وحسابات One Piece Bounty Rush 🏴‍☠️</b>\n\n"
150
- "يمكنك جمع النقاط من التفاعل والإحالات واستبدالها بحسابات ومكافآت."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  )
152
-
153
- kb = main_menu_kb()
154
- if user_id in OWNERS or await db.admins.find_one({"user_id": user_id}):
155
- kb.inline_keyboard.append([InlineKeyboardButton(text="👑 لوحة المطورين", callback_data="admin_panel")])
156
-
157
- await message.answer(text, reply_markup=kb)
158
-
159
- @router.callback_query(F.data == "main_menu")
160
- async def back_to_main(call: CallbackQuery):
161
- user_id = call.from_user.id
162
- text = "<b>القائمة الرئيسية:</b>"
163
- kb = main_menu_kb()
164
- if user_id in OWNERS or await db.admins.find_one({"user_id": user_id}):
165
- kb.inline_keyboard.append([InlineKeyboardButton(text="👑 لوحة المطورين", callback_data="admin_panel")])
166
- await call.message.edit_text(text, reply_markup=kb)
167
-
168
- @router.message(F.text & ~F.text.startswith('/'))
169
- async def process_message(message: Message):
170
- if message.chat.type in ['group', 'supergroup']:
171
- user_id = message.from_user.id
172
- user = await get_user(user_id)
173
- settings = await get_settings()
174
-
175
- now = time.time()
176
- if now - user.get("last_msg_time", 0) > settings.get("cooldown_seconds", 60):
177
- xp_to_add = settings.get("msg_xp", 5)
178
- new_xp = user["xp"] + xp_to_add
179
- new_level = int(new_xp / 100) + 1
180
-
181
- update_data = {"xp": new_xp, "last_msg_time": now}
182
- if new_level > user.get("level", 1):
183
- update_data["level"] = new_level
184
- update_data["points"] = user["points"] + (new_level * 100)
185
- await log_transaction(user_id, new_level * 100, "add", f"مكافأة الوصول للمستوى {new_level}")
186
  try:
187
- await message.reply(f"🎉 مبروك! لقد وصلت إلى المستوى <b>{new_level}</b> وحصلت على مكافأة نقاط.")
188
- except: pass
189
-
190
- await db.users.update_one({"user_id": user_id}, {"$set": update_data})
191
-
192
- @router.callback_query(F.data == "my_profile")
193
- async def show_profile(call: CallbackQuery):
194
- user = await get_user(call.from_user.id)
195
- bot_info = await bot.get_me()
196
- ref_link = f"https://t.me/{bot_info.username}?start=ref_{user['user_id']}"
197
-
198
- text = (
199
- f"👤 <b>حسابك:</b>\n"
200
- f"🪙 النقاط: <code>{user.get('points', 0)}</code>\n"
201
- f"⭐ المستوى: {user.get('level', 1)} (XP: {user.get('xp', 0)})\n"
202
- f"💎 VIP: {'نعم' if user.get('vip', 0) > 0 else 'لا'}\n"
203
- f"👥 الإحالات: {user.get('referrals', 0)}\n\n"
204
- f"🔗 <b>رابط الإحالة الخاص بك:</b>\n<code>{ref_link}</code>"
 
205
  )
206
- kb = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🔙 عودة للقائمة", callback_data="main_menu")]])
207
- await call.message.edit_text(text, reply_markup=kb)
 
 
 
 
208
 
209
- @router.callback_query(F.data == "daily_reward")
210
- async def claim_daily(call: CallbackQuery):
211
- user_id = call.from_user.id
212
- user = await get_user(user_id)
213
- settings = await get_settings()
214
-
215
- now = datetime.utcnow()
216
- last_daily = user.get("last_daily")
217
-
218
- if last_daily and (now - last_daily).days < 1:
219
- await call.answer("⏳ لقد استلمت المكافأة اليوم! عد غداً.", show_alert=True)
220
- return
221
-
222
- reward = settings.get("daily_reward", 1000)
223
- await db.users.update_one(
224
- {"user_id": user_id},
225
- {"$inc": {"points": reward}, "$set": {"last_daily": now}}
226
  )
227
- await log_transaction(user_id, reward, "add", "المكافأة اليومية")
228
- await call.answer(f"🎉 استلمت {reward} نقطة!", show_alert=True)
229
- await show_profile(call)
230
-
231
- @router.callback_query(F.data == "sell_acc")
232
- async def sell_account(call: CallbackQuery):
233
- text = f"💰 لبيع حسابك مقابل نقاط أو أموال، يرجى التواصل مع الإدارة عبر:\n\n{SELL_ACCOUNT}"
234
- kb = InlineKeyboardMarkup(inline_keyboard=[
235
- [InlineKeyboardButton(text="تواصل الآن", url=f"https://t.me/{SELL_ACCOUNT.replace('@', '')}")],
236
- [InlineKeyboardButton(text="🔙 عودة", callback_data="main_menu")]
237
- ])
238
- await call.message.edit_text(text, reply_markup=kb)
239
-
240
- @router.callback_query(F.data == "mystery_box")
241
- async def open_box(call: CallbackQuery):
242
- settings = await get_settings()
243
- price = settings.get("box_price", 2000)
244
- user_id = call.from_user.id
245
-
246
- user = await db.users.find_one_and_update(
 
 
 
247
  {"user_id": user_id, "points": {"$gte": price}},
248
  {"$inc": {"points": -price}},
249
- return_document=True
250
  )
251
-
252
- if not user:
253
- await call.answer("❌ نقاطك لا تكفي لفتح الصندوق!", show_alert=True)
254
- return
255
-
256
- await log_transaction(user_id, price, "sub", "شراء صندوق حظ")
257
-
258
- prizes = [
259
- ("نقاط", 500, 40), ("نقاط", 3000, 30),
260
- ("XP", 500, 20), ("تذكرة سحب", 1, 9), ("نقاط", 10000, 1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  ]
262
-
263
- prize_type, amount, _ = random.choices(prizes, weights=[p[2] for p in prizes])[0]
264
-
265
- if prize_type == "نقاط":
266
- await db.users.update_one({"user_id": user_id}, {"$inc": {"points": amount}})
267
- await log_transaction(user_id, amount, "add", "جائزة صندوق الحظ")
268
- elif prize_type == "XP":
269
- await db.users.update_one({"user_id": user_id}, {"$inc": {"xp": amount}})
270
- elif prize_type == "تذكرة سحب":
271
- await db.users.update_one({"user_id": user_id}, {"$inc": {"tickets": amount}})
272
-
273
- await call.answer(f"🎉 فتحت الصندوق وربحت: {amount} {prize_type}!", show_alert=True)
274
- await show_profile(call)
275
-
276
- @router.callback_query(F.data == "store")
277
- async def show_store(call: CallbackQuery):
278
- products = await db.products.find({"status": "متوفر"}).to_list(10)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  if not products:
280
- kb = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🔙 عودة للقائمة", callback_data="main_menu")]])
281
- await call.message.edit_text("🛒 المتجر فارغ حالياً.", reply_markup=kb)
282
  return
283
-
284
- kb = InlineKeyboardMarkup(inline_keyboard=[])
285
  for p in products:
286
- kb.inline_keyboard.append([InlineKeyboardButton(text=f"{p['name']} - {p['price']} نقطة", callback_data=f"view_prod_{p['_id']}")])
287
- kb.inline_keyboard.append([InlineKeyboardButton(text="🔙 عودة", callback_data="main_menu")])
288
-
289
- await call.message.edit_text("🛒 <b>حسابات Bounty Rush المتوفرة:</b>\nاختر حساباً للتفاصيل:", reply_markup=kb)
290
-
291
- @router.callback_query(F.data.startswith("view_prod_"))
292
- async def view_product(call: CallbackQuery):
293
- from bson import ObjectId
294
- prod_id = call.data.split("_")[2]
295
- product = await db.products.find_one({"_id": ObjectId(prod_id)})
296
-
297
- if not product or product['status'] != 'متوفر':
298
- await call.answer("❌ هذا الحساب لم يعد متوفراً.", show_alert=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  return
300
-
301
- text = (
302
- f"🏷️ <b>{product['name']}</b>\n"
303
- f"💰 السعر: {product['price']} نقطة\n\n"
304
- f"📝 الوصف:\n{product.get('description', 'لا يوجد وصف')}"
 
 
 
 
 
 
 
 
 
305
  )
306
- kb = InlineKeyboardMarkup(inline_keyboard=[
307
- [InlineKeyboardButton(text="✅ تأكيد الشراء", callback_data=f"buy_prod_{prod_id}")],
308
- [InlineKeyboardButton(text="🔙 عودة للمتجر", callback_data="store")]
309
- ])
310
- await call.message.edit_text(text, reply_markup=kb)
311
-
312
- @router.callback_query(F.data.startswith("buy_prod_"))
313
- async def buy_product(call: CallbackQuery):
314
- from bson import ObjectId
315
- user_id = call.from_user.id
316
- prod_id = call.data.split("_")[2]
317
-
318
- product = await db.products.find_one({"_id": ObjectId(prod_id)})
319
- if not product or product['status'] != 'متوفر':
320
- await call.answer("❌ الحساب مباع أو غير متوفر.", show_alert=True)
321
  return
322
-
323
- price = product['price']
324
-
325
- user = await db.users.find_one_and_update(
326
- {"user_id": user_id, "points": {"$gte": price}},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  {"$inc": {"points": -price}},
328
- return_document=True
329
  )
330
-
331
- if not user:
332
- await call.answer("❌ رصيدك من النقاط لا يكفي لإتمام الشراء.", show_alert=True)
333
  return
334
-
335
- updated_product = await db.products.find_one_and_update(
336
- {"_id": ObjectId(prod_id), "status": "متوفر"},
337
- {"$set": {"status": "مباع", "buyer_id": user_id, "sell_date": datetime.utcnow()}}
338
  )
339
-
340
- if not updated_product:
341
- await db.users.update_one({"user_id": user_id}, {"$inc": {"points": price}})
342
- await call.answer("❌ نعتذر، قام شخص آخر بشراء الحساب قبلك بلحظات. تم إرجاع نقاطك.", show_alert=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  return
344
-
345
- await log_transaction(user_id, price, "sub", f"شراء حساب {product['name']}")
346
-
347
- account_details = product.get("account_data", "الرجاء التواصل مع الإدارة لاستلام بيانات الحساب.")
348
- text = f"✅ <b>تم الشراء بنجاح!</b>\n\nبيانات الحساب:\n<code>{account_details}</code>"
349
- kb = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🔙 القائمة الرئيسية", callback_data="main_menu")]])
350
- await call.message.edit_text(text, reply_markup=kb)
351
-
352
- for admin in OWNERS:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  try:
354
- await bot.send_message(admin, f"🛍️ <b>عملية شراء جديدة:</b>\nالمستخدم: {user_id}\nالمنتج: {product['name']}\nالسعر: {price}")
355
- except: pass
356
-
357
- @router.callback_query(F.data == "admin_panel")
358
- async def admin_panel(call: CallbackQuery):
359
- if call.from_user.id not in OWNERS and not await db.admins.find_one({"user_id": call.from_user.id}):
360
- return await call.answer("⛔ ليس لديك صلاحية.", show_alert=True)
361
- await call.message.edit_text("👑 <b>لوحة المطورين:</b>\nتحكم بالبوت بالكامل من هنا.", reply_markup=admin_menu_kb())
362
-
363
- @router.callback_query(F.data == "admin_stats")
364
- async def admin_stats(call: CallbackQuery):
365
- users_count = await db.users.count_documents({})
366
- prods_count = await db.products.count_documents({"status": "متوفر"})
367
- sold_count = await db.products.count_documents({"status": "مباع"})
368
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  text = (
370
- "📊 <b>إحصائيات البوت:</b>\n\n"
371
- f"👥 إجمالي المستخدمين: {users_count}\n"
372
- f"🛒 حسابات متوفرة: {prods_count}\n"
373
- f"🛍️ حسابات مباعة: {sold_count}"
374
  )
375
- kb = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🔙 رجوع", callback_data="admin_panel")]])
376
- await call.message.edit_text(text, reply_markup=kb)
377
-
378
- # --- إعداد Web Server لـ Hugging Face ---
379
- async def health_check(request):
380
- return web.json_response({"status": "ok", "time": datetime.utcnow().isoformat()})
381
-
382
- async def run_web_server():
383
- app = web.Application()
384
- app.router.add_get('/health', health_check)
385
- app.router.add_get('/', health_check)
386
-
387
- runner = web.AppRunner(app)
388
- await runner.setup()
389
- site = web.TCPSite(runner, '0.0.0.0', 7860)
390
- await site.start()
391
- logger.info("✅ HTTP Health Server is running on http://0.0.0.0:7860")
392
-
393
- while True:
394
- await asyncio.sleep(3600)
395
-
396
- # --- الدالة الرئيسية مع معالجة الأخطاء والتنظيف ---
397
- async def main():
398
- logger.info("🚀 جاري بدء تشغيل البوت وتهيئة قاعدة البيانات...")
399
- await init_db()
400
-
401
- dp.include_router(router)
402
-
403
- # محاولة تنظيف الويب هوك مع تجنب توقف الكود عند بطء الشبكة
404
- retries = 3
405
- for i in range(retries):
406
- try:
407
- await bot.delete_webhook(drop_pending_updates=True, request_timeout=30)
408
- logger.info("🧹 تم تنظيف الويب هوك القديم بنجاح.")
409
- break
410
- except Exception as e:
411
- logger.warning(f"⚠️ محاولة اتصال ({i+1}/{retries}) فشلت مؤقتاً: {e}")
412
- if i == retries - 1:
413
- logger.error("❌ فشل الاتصال بخوادم تيليجرام، سيتم تشغيل البوت عبر Polling مباشرة...")
414
- else:
415
- await asyncio.sleep(5)
416
-
417
- # تشغيل السيرفر والبوت بالتوازي
418
- task_web = asyncio.create_task(run_web_server())
419
- task_bot = asyncio.create_task(dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types()))
420
-
421
- await asyncio.gather(task_web, task_bot)
422
-
423
- if __name__ == "__main__":
424
- try:
425
- asyncio.run(main())
426
- except (KeyboardInterrupt, SystemExit):
427
- logger.info("🛑 تم إيقاف البوت بنجاح.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🎮 بوت تعويضات حسابات One Piece Bounty Rush
3
+ ====================================================
4
+ ملف واحد يحتوي على كامل منطق البوت (نقاط / XP / متجر / VIP / إحالات /
5
+ أكواد خصم / Mystery Box / سحوبات / Leaderboard / إنجازات / لوحة مطورين).
6
+
7
+ كل البيانات تُحفظ في MongoDB فقط. لا يوجد تخزين محلي (RAM / ملفات / SQLite).
8
+ مصمم للعمل على Hugging Face Docker Spaces:
9
+ - sdk: docker
10
+ - app_port: 7860 (القيمة الافتراضية الحالية في توثيق Hugging Face)
11
+ - يستمع على 0.0.0.0
12
+ - يشغّل HTTP health server (GET /health) بالتوازي مع Telegram polling
13
+ - يقرأ كل الأسرار عبر os.getenv() فقط (BOT_TOKEN, MONGO_URI, ...)
14
+ """
15
+
16
  import os
17
  import sys
18
+ import json
19
  import random
 
20
  import asyncio
21
+ import logging
22
+ import signal
23
+ from datetime import datetime, timedelta, timezone
24
+ from typing import Optional, Dict, Any, List
25
+
26
  from aiohttp import web
27
+ from pymongo import ASCENDING, DESCENDING, ReturnDocument
28
+ from pymongo.errors import DuplicateKeyError, PyMongoError
29
  from motor.motor_asyncio import AsyncIOMotorClient
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ from telegram import (
32
+ Update,
33
+ InlineKeyboardMarkup,
34
+ InlineKeyboardButton,
35
+ ReplyKeyboardMarkup,
36
+ KeyboardButton,
37
+ )
38
+ from telegram.constants import ParseMode
39
+ from telegram.ext import (
40
+ Application,
41
+ ApplicationBuilder,
42
+ CommandHandler,
43
+ MessageHandler,
44
+ CallbackQueryHandler,
45
+ ContextTypes,
46
+ filters,
47
+ )
48
+ from telegram.error import TelegramError
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # Logging — لا تُطبع أبداً أي Secret أو بيانات اعتماد
52
+ # ---------------------------------------------------------------------------
53
+ logging.basicConfig(
54
+ format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
55
+ level=logging.INFO,
56
+ )
57
+ logging.getLogger("httpx").setLevel(logging.WARNING)
58
+ logging.getLogger("apscheduler").setLevel(logging.WARNING)
59
+ logger = logging.getLogger("bounty-rush-bot")
60
+
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # قراءة الإعدادات من Environment Variables / Hugging Face Secrets فقط
64
+ # ---------------------------------------------------------------------------
65
+ def _require_env(name: str) -> str:
66
+ value = os.getenv(name)
67
+ if not value or not value.strip():
68
+ logger.critical(
69
+ "متغير البيئة المطلوب '%s' غير موجود. "
70
+ "أضفه من Space Settings -> Secrets ثم أعد تشغيل الـ Space.",
71
+ name,
72
+ )
73
+ sys.exit(1)
74
+ return value.strip()
75
+
76
+
77
+ BOT_TOKEN = _require_env("BOT_TOKEN")
78
+ MONGO_URI = _require_env("MONGO_URI")
79
+ MONGO_DB_NAME = os.getenv("MONGO_DB_NAME", "bounty_rush_bot")
80
+ PORT = int(os.getenv("PORT", "7860")) # منفذ Hugging Face الافتراضي الحالي
81
+
82
+ # معرّفات المالكين — أرقام Telegram User ID فقط، ليست Secrets
83
+ OWNER_IDS = {6069597294, 5359398024, 7126816492}
84
+ SELL_ACCOUNT_CONTACT = "@kk66kk6"
85
+
86
+ ALL_ROLES = [
87
+ "super_admin",
88
+ "store_admin",
89
+ "points_admin",
90
+ "vip_admin",
91
+ "giveaway_admin",
92
+ "user_admin",
93
+ "moderator",
94
+ ]
95
+
96
+ DEFAULT_SETTINGS: Dict[str, Any] = {
97
+ "_id": "config",
98
+ "bot_name": "🎮 One Piece Bounty Rush Store",
99
+ "points": {
100
+ "referral": 500,
101
+ "message_interaction": 5,
102
+ "interaction_cooldown_seconds": 60,
103
+ "daily_reward": 1000,
104
+ "weekly_reward": 5000,
105
+ },
106
+ "xp": {
107
+ "per_interaction": 10,
108
+ "interaction_cooldown_seconds": 60,
109
+ },
110
+ "levels": [
111
+ {"level": 1, "xp_required": 0, "points_reward": 0, "discount_percent": 0},
112
+ {"level": 2, "xp_required": 200, "points_reward": 100, "discount_percent": 0},
113
+ {"level": 3, "xp_required": 500, "points_reward": 200, "discount_percent": 1},
114
+ {"level": 5, "xp_required": 1500, "points_reward": 500, "discount_percent": 2},
115
+ {"level": 10, "xp_required": 5000, "points_reward": 1500, "discount_percent": 5},
116
+ ],
117
+ "links": {
118
+ "group": "",
119
+ "channel": "",
120
+ "sell_account_contact": SELL_ACCOUNT_CONTACT,
121
+ },
122
+ "messages": {
123
+ "welcome": "أهلاً بك في متجر حسابات ون بيس باونتي راش 🎮",
124
+ },
125
+ "mystery_box_enabled": True,
126
+ "giveaways_enabled": True,
127
+ }
128
+
129
+ # state بسيطة (ليست بيانات دائمة) لتتبّع خطوات إدخال الأدمن التفاعلية
130
+ PENDING: Dict[int, Dict[str, Any]] = {}
131
+ # تتبّع بسيط للتفاعل الأخير في الذاكرة كخط دفاع إضافي فوق ما هو محفوظ في DB
132
+ LAST_SEEN: Dict[int, datetime] = {}
133
+
134
+
135
+ def now_utc() -> datetime:
136
+ return datetime.now(timezone.utc)
137
+
138
+
139
+ def gen_code(length: int = 6) -> str:
140
+ import string as _s
141
+
142
+ return "".join(random.SystemRandom().choice(_s.ascii_uppercase + _s.digits) for _ in range(length))
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # طبقة الوصول لقاعدة البيانات
147
+ # ---------------------------------------------------------------------------
148
+ class DB:
149
+ def __init__(self, uri: str, name: str):
150
+ self.client = AsyncIOMotorClient(uri, serverSelectionTimeoutMS=8000)
151
+ self.db = self.client[name]
152
+
153
+ self.users = self.db["users"]
154
+ self.transactions = self.db["transactions"]
155
+ self.referrals = self.db["referrals"]
156
+ self.products = self.db["products"]
157
+ self.orders = self.db["orders"]
158
+ self.sell_requests = self.db["sell_requests"]
159
+ self.promo_codes = self.db["promo_codes"]
160
+ self.promo_code_usage = self.db["promo_code_usage"]
161
+ self.achievements = self.db["achievements"]
162
+ self.vip_plans = self.db["vip_plans"]
163
+ self.mystery_boxes = self.db["mystery_boxes"]
164
+ self.mystery_box_openings = self.db["mystery_box_openings"]
165
+ self.giveaways = self.db["giveaways"]
166
+ self.giveaway_entries = self.db["giveaway_entries"]
167
+ self.leaderboard_seasons = self.db["leaderboard_seasons"]
168
+ self.settings = self.db["settings"]
169
+ self.admins = self.db["admins"]
170
+ self.audit_logs = self.db["audit_logs"]
171
+
172
+ async def ping(self):
173
+ await self.client.admin.command("ping")
174
+
175
+ async def ensure_indexes(self):
176
+ await self.users.create_index("user_id", unique=True)
177
+ await self.users.create_index("referred_by")
178
+ await self.transactions.create_index("user_id")
179
+ await self.transactions.create_index(
180
+ "reference_id", unique=True, sparse=True
181
+ )
182
+ await self.referrals.create_index("referred_id", unique=True)
183
+ await self.referrals.create_index("referrer_id")
184
+ await self.products.create_index("status")
185
+ await self.orders.create_index("user_id")
186
+ await self.orders.create_index("product_id")
187
+ await self.promo_codes.create_index("code", unique=True)
188
+ await self.promo_code_usage.create_index(
189
+ [("code", ASCENDING), ("user_id", ASCENDING)], unique=True
190
+ )
191
+ await self.giveaway_entries.create_index(
192
+ [("giveaway_id", ASCENDING), ("user_id", ASCENDING)], unique=True
193
+ )
194
+ await self.mystery_box_openings.create_index("user_id")
195
+ await self.audit_logs.create_index([("created_at", DESCENDING)])
196
+ await self.admins.create_index("user_id", unique=True)
197
+
198
+ async def close(self):
199
+ self.client.close()
200
+
201
+
202
+ db: Optional[DB] = None
203
+ _settings_cache: Dict[str, Any] = {}
204
+ _settings_cache_at: Optional[datetime] = None
205
+
206
+
207
+ async def get_settings() -> Dict[str, Any]:
208
+ """يقرأ الإعدادات من MongoDB مع كاش قصير لتقليل الاستعلامات."""
209
+ global _settings_cache, _settings_cache_at
210
+ if _settings_cache_at and (now_utc() - _settings_cache_at).total_seconds() < 5:
211
+ return _settings_cache
212
+ doc = await db.settings.find_one({"_id": "config"})
213
+ if not doc:
214
+ doc = DEFAULT_SETTINGS.copy()
215
+ await db.settings.insert_one(doc)
216
+ _settings_cache = doc
217
+ _settings_cache_at = now_utc()
218
+ return doc
219
+
220
+
221
+ async def update_settings_section(section: str, value: Any):
222
+ await db.settings.update_one(
223
+ {"_id": "config"}, {"$set": {section: value}}, upsert=True
224
+ )
225
+ global _settings_cache_at
226
+ _settings_cache_at = None
227
+
228
+
229
+ # ---------------------------------------------------------------------------
230
+ # الصلاحيات
231
+ # ---------------------------------------------------------------------------
232
+ async def get_admin_doc(user_id: int) -> Optional[Dict[str, Any]]:
233
+ return await db.admins.find_one({"user_id": user_id})
234
+
235
+
236
+ async def is_owner(user_id: int) -> bool:
237
+ return user_id in OWNER_IDS
238
+
239
+
240
+ async def get_roles(user_id: int) -> List[str]:
241
+ if user_id in OWNER_IDS:
242
+ return ["super_admin"]
243
+ doc = await get_admin_doc(user_id)
244
+ if not doc:
245
+ return []
246
+ return doc.get("roles", [])
247
+
248
+
249
+ async def has_permission(user_id: int, required: Optional[List[str]] = None) -> bool:
250
+ roles = await get_roles(user_id)
251
+ if "super_admin" in roles:
252
+ return True
253
+ if not required:
254
+ return len(roles) > 0
255
+ return any(r in roles for r in required)
256
+
257
+
258
+ async def audit(admin_id: int, action: str, details: Dict[str, Any]):
259
+ await db.audit_logs.insert_one(
260
+ {
261
+ "admin_id": admin_id,
262
+ "action": action,
263
+ "details": details,
264
+ "created_at": now_utc(),
265
  }
266
+ )
267
+
268
+
269
+ # ---------------------------------------------------------------------------
270
+ # المستخدمون
271
+ # ---------------------------------------------------------------------------
272
+ def default_user_doc(user_id: int, username: str, first_name: str) -> Dict[str, Any]:
273
+ return {
274
+ "user_id": user_id,
275
+ "username": username or "",
276
+ "first_name": first_name or "",
277
+ "points": 0,
278
+ "xp": 0,
279
+ "level": 1,
280
+ "levels_rewarded": [],
281
+ "vip": None, # {"plan_id":..., "expires_at":...}
282
+ "tickets": 0,
283
+ "referred_by": None,
284
+ "referrals_count": 0,
285
+ "purchases_count": 0,
286
+ "sales_count": 0,
287
+ "achievements": [],
288
+ "banned": False,
289
+ "frozen": False,
290
+ "last_daily_claim": None,
291
+ "last_weekly_claim": None,
292
+ "last_interaction_at": None,
293
+ "personal_code": None,
294
+ "created_at": now_utc(),
295
+ "updated_at": now_utc(),
296
+ }
297
+
298
 
299
+ async def get_or_create_user(user_id: int, username: str = "", first_name: str = "") -> Dict[str, Any]:
300
+ doc = await db.users.find_one({"user_id": user_id})
301
+ if doc:
302
+ upd = {}
303
+ if username and doc.get("username") != username:
304
+ upd["username"] = username
305
+ if first_name and doc.get("first_name") != first_name:
306
+ upd["first_name"] = first_name
307
+ if upd:
308
+ upd["updated_at"] = now_utc()
309
+ await db.users.update_one({"user_id": user_id}, {"$set": upd})
310
+ doc.update(upd)
311
+ return doc
312
+ new_doc = default_user_doc(user_id, username, first_name)
313
+ try:
314
+ await db.users.insert_one(new_doc)
315
+ except DuplicateKeyError:
316
+ return await db.users.find_one({"user_id": user_id})
317
+ return new_doc
318
+
319
+
320
+ async def add_points(
321
+ user_id: int,
322
+ amount: int,
323
+ ptype: str,
324
+ reason: str,
325
+ admin_id: Optional[int] = None,
326
+ reference_id: Optional[str] = None,
327
+ ) -> bool:
328
+ """يضيف/يخصم نقاط مع دفتر معاملات، ويمنع التكرار عبر reference_id فريد."""
329
+ tx = {
330
  "user_id": user_id,
331
  "amount": amount,
332
+ "type": ptype,
333
  "reason": reason,
334
+ "admin_id": admin_id,
335
+ "reference_id": reference_id,
336
+ "timestamp": now_utc(),
337
+ }
338
+ if reference_id:
339
+ try:
340
+ await db.transactions.insert_one(tx)
341
+ except DuplicateKeyError:
342
+ # هذه العملية سُجلت من قبل — لا تكرر إضافة النقاط
343
+ return False
344
+ else:
345
+ await db.transactions.insert_one(tx)
346
+
347
+ await db.users.update_one(
348
+ {"user_id": user_id}, {"$inc": {"points": amount}, "$set": {"updated_at": now_utc()}}
349
+ )
350
+ return True
351
+
352
+
353
+ async def add_xp(user_id: int, amount: int, bot=None, chat_id: Optional[int] = None):
 
 
 
 
 
 
 
 
 
 
 
354
  user = await db.users.find_one({"user_id": user_id})
355
  if not user:
356
+ return
357
+ new_xp = user.get("xp", 0) + amount
358
+ await db.users.update_one({"user_id": user_id}, {"$set": {"xp": new_xp, "updated_at": now_utc()}})
359
+ await check_level_up(user_id, new_xp, bot)
 
 
 
 
 
 
 
 
 
 
360
 
361
+
362
+ async def check_level_up(user_id: int, xp: int, bot=None):
363
+ settings = await get_settings()
364
+ levels = sorted(settings.get("levels", []), key=lambda l: l["xp_required"])
365
+ user = await db.users.find_one({"user_id": user_id})
366
+ if not user:
367
+ return
368
+ eligible = [l for l in levels if xp >= l["xp_required"]]
369
+ if not eligible:
370
+ return
371
+ top = eligible[-1]
372
+ already_rewarded = set(user.get("levels_rewarded", []))
373
+ if top["level"] != user.get("level"):
374
+ await db.users.update_one({"user_id": user_id}, {"$set": {"level": top["level"]}})
375
+ if top["level"] not in already_rewarded:
376
+ reward = top.get("points_reward", 0)
377
+ if reward:
378
+ await add_points(
379
+ user_id, reward, "level_up", f"مكافأة الوصول للمستوى {top['level']}",
380
+ reference_id=f"levelup:{user_id}:{top['level']}",
381
+ )
382
+ await db.users.update_one(
383
+ {"user_id": user_id}, {"$addToSet": {"levels_rewarded": top["level"]}}
384
+ )
385
+ if bot:
386
+ await safe_send(
387
+ bot, user_id,
388
+ f"⭐ مبروك! وصلت إلى Level {top['level']} وحصلت على {reward} نقطة إضافية!",
389
+ )
390
+ await check_achievements(user_id, bot)
391
+
392
+
393
+ async def check_achievements(user_id: int, bot=None):
394
+ user = await db.users.find_one({"user_id": user_id})
395
+ if not user:
396
+ return
397
+ already = set(user.get("achievements", []))
398
+ async for ach in db.achievements.find({"enabled": {"$ne": False}}):
399
+ aid = ach["_id"]
400
+ if aid in already:
401
+ continue
402
+ cond = ach.get("condition", {})
403
+ ctype = cond.get("type")
404
+ ok = False
405
+ if ctype == "points_total" and user.get("points", 0) >= cond.get("value", 0):
406
+ ok = True
407
+ elif ctype == "level" and user.get("level", 1) >= cond.get("value", 0):
408
+ ok = True
409
+ elif ctype == "referrals_count" and user.get("referrals_count", 0) >= cond.get("value", 0):
410
+ ok = True
411
+ elif ctype == "purchases_count" and user.get("purchases_count", 0) >= cond.get("value", 0):
412
+ ok = True
413
+ elif ctype == "sales_count" and user.get("sales_count", 0) >= cond.get("value", 0):
414
+ ok = True
415
+ if ok:
416
+ await db.users.update_one({"user_id": user_id}, {"$addToSet": {"achievements": aid}})
417
+ reward = ach.get("points_reward", 0)
418
+ if reward:
419
+ await add_points(
420
+ user_id, reward, "achievement", f"إنجاز: {ach.get('name')}",
421
+ reference_id=f"ach:{user_id}:{aid}",
422
+ )
423
+ if bot:
424
+ await safe_send(bot, user_id, f"🏅 إنجاز جديد: {ach.get('name')} (+{reward} نقطة)")
425
+
426
+
427
+ async def safe_send(bot, chat_id: int, text: str, **kwargs):
428
+ try:
429
+ await bot.send_message(chat_id=chat_id, text=text, **kwargs)
430
+ except TelegramError as e:
431
+ logger.warning("فشل إرسال رسالة إلى %s: %s", chat_id, e.__class__.__name__)
432
+ except Exception as e: # noqa: BLE001
433
+ logger.warning("خطأ غير متوقع أثناء الإرسال: %s", e.__class__.__name__)
434
+
435
+
436
+ # ---------------------------------------------------------------------------
437
+ # الإحالات
438
+ # ---------------------------------------------------------------------------
439
+ async def process_referral(new_user_id: int, referrer_id: int, bot=None) -> bool:
440
+ if new_user_id == referrer_id:
441
+ return False
442
+ referrer = await db.users.find_one({"user_id": referrer_id})
443
+ if not referrer:
444
+ return False
445
+ try:
446
+ await db.referrals.insert_one(
447
+ {
448
+ "referrer_id": referrer_id,
449
+ "referred_id": new_user_id,
450
+ "created_at": now_utc(),
451
+ }
452
+ )
453
+ except DuplicateKeyError:
454
+ # هذا المستخدم لديه إحالة مسجّلة مسبقاً — امنع التكرار
455
+ return False
456
+
457
+ await db.users.update_one({"user_id": new_user_id}, {"$set": {"referred_by": referrer_id}})
458
+ await db.users.update_one({"user_id": referrer_id}, {"$inc": {"referrals_count": 1}})
459
+
460
+ settings = await get_settings()
461
+ reward = settings.get("points", {}).get("referral", 0)
462
+ if reward:
463
+ await add_points(
464
+ referrer_id, reward, "referral", f"إحالة مستخدم جديد {new_user_id}",
465
+ reference_id=f"ref:{referrer_id}:{new_user_id}",
466
+ )
467
+ if bot:
468
+ await safe_send(bot, referrer_id, f"👥 حصلت على {reward} نقطة من إحالة جديدة!")
469
+ await check_achievements(referrer_id, bot)
470
+ return True
471
+
472
+
473
+ # ---------------------------------------------------------------------------
474
+ # المتجر
475
+ # ---------------------------------------------------------------------------
476
+ async def purchase_product(user_id: int, product_id: str, bot=None, coupon_code: Optional[str] = None):
477
+ product = await db.products.find_one_and_update(
478
+ {"_id": product_id, "status": "متوفر"},
479
+ {"$set": {"status": "محجوز", "reserved_by": user_id, "reserved_at": now_utc()}},
480
+ return_document=ReturnDocument.AFTER,
481
  )
482
+ if not product:
483
+ return False, "❌ هذا الحساب لم يعد متوفراً (ربما اشتراه شخص آخر أو لم يعد موجوداً)."
484
+
485
+ price = product.get("price", 0)
486
+ # تحقق من Flash Sale ومن انتهاء صلاحيته لحظة الشراء
487
+ flash = product.get("flash_sale")
488
+ if flash and flash.get("expires_at") and flash["expires_at"] > now_utc() and flash.get("remaining", 0) > 0:
489
+ price = flash.get("flash_price", price)
490
+
491
+ discount_percent = 0
492
+ if coupon_code:
493
+ code_doc = await db.promo_codes.find_one({"code": coupon_code.upper(), "active": True})
494
+ if code_doc:
495
+ expired = code_doc.get("expires_at") and code_doc.get("expires_at") < now_utc()
496
+ allowed_users = code_doc.get("allowed_users") or []
497
+ user_allowed = (not allowed_users) or (user_id in allowed_users)
498
+ if not expired and user_allowed and code_doc.get("used_count", 0) < code_doc.get("max_uses", 10**9):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  try:
500
+ await db.promo_code_usage.insert_one({"code": code_doc["code"], "user_id": user_id, "used_at": now_utc()})
501
+ discount_percent = code_doc.get("discount_percent", 0)
502
+ await db.promo_codes.update_one({"_id": code_doc["_id"]}, {"$inc": {"used_count": 1}})
503
+ owner_reward = code_doc.get("owner_points_reward", 0)
504
+ if owner_reward and code_doc.get("owner_id"):
505
+ await add_points(
506
+ code_doc["owner_id"], owner_reward, "promo_code_used",
507
+ f"استخدام الكود {code_doc['code']}",
508
+ reference_id=f"promo:{code_doc['code']}:{user_id}",
509
+ )
510
+ except DuplicateKeyError:
511
+ pass # المستخدم استخدم هذا الكود من قبل
512
+
513
+ final_price = max(0, round(price * (1 - discount_percent / 100)))
514
+
515
+ charged = await db.users.find_one_and_update(
516
+ {"user_id": user_id, "points": {"$gte": final_price}},
517
+ {"$inc": {"points": -final_price, "purchases_count": 1}},
518
+ return_document=ReturnDocument.AFTER,
519
  )
520
+ if not charged:
521
+ # فشل الدفع → أعد المنتج متاحاً
522
+ await db.products.update_one(
523
+ {"_id": product_id}, {"$set": {"status": "متوفر"}, "$unset": {"reserved_by": "", "reserved_at": ""}}
524
+ )
525
+ return False, f"❌ رصيدك من النقاط غير كافٍ (السعر: {final_price})."
526
 
527
+ await db.products.update_one({"_id": product_id}, {"$set": {"status": "مباع", "sold_to": user_id, "sold_at": now_utc()}})
528
+ if flash:
529
+ await db.products.update_one({"_id": product_id}, {"$inc": {"flash_sale.remaining": -1}})
530
+
531
+ order_id = f"order:{product_id}:{user_id}:{int(now_utc().timestamp())}"
532
+ await db.orders.insert_one(
533
+ {
534
+ "_id": order_id,
535
+ "user_id": user_id,
536
+ "product_id": product_id,
537
+ "price_paid": final_price,
538
+ "discount_percent": discount_percent,
539
+ "created_at": now_utc(),
540
+ }
 
 
 
541
  )
542
+ await add_points(user_id, -0, "purchase_log", f"شراء منتج {product_id}", reference_id=f"buylog:{order_id}")
543
+ await check_achievements(user_id, bot)
544
+ return True, product
545
+
546
+
547
+ async def revert_expired_flash_sales():
548
+ try:
549
+ cursor = db.products.find({"flash_sale.expires_at": {"$lt": now_utc()}, "status": "متوفر"})
550
+ async for p in cursor:
551
+ await db.products.update_one({"_id": p["_id"]}, {"$unset": {"flash_sale": ""}})
552
+ except PyMongoError as e:
553
+ logger.warning("تعذر فحص العروض المنتهية: %s", e.__class__.__name__)
554
+
555
+
556
+ # ---------------------------------------------------------------------------
557
+ # Mystery Box
558
+ # ---------------------------------------------------------------------------
559
+ async def open_mystery_box(user_id: int, box_id: str):
560
+ box = await db.mystery_boxes.find_one({"_id": box_id, "enabled": True})
561
+ if not box:
562
+ return False, "❌ هذا الصندوق غير متاح حالياً."
563
+ price = box.get("price", 0)
564
+ charged = await db.users.find_one_and_update(
565
  {"user_id": user_id, "points": {"$gte": price}},
566
  {"$inc": {"points": -price}},
567
+ return_document=ReturnDocument.AFTER,
568
  )
569
+ if not charged:
570
+ return False, f"❌ رصيدك غير كافٍ لفتح هذا الصندوق (السعر: {price})."
571
+
572
+ prizes = box.get("prizes", [])
573
+ if not prizes:
574
+ return False, "❌ هذا الصندوق لا يحتوي على جوائز مُعرّفة."
575
+ weights = [p.get("probability", 1) for p in prizes]
576
+ prize = random.SystemRandom().choices(prizes, weights=weights, k=1)[0]
577
+
578
+ result = {"box_id": box_id, "user_id": user_id, "prize": prize, "opened_at": now_utc()}
579
+ await db.mystery_box_openings.insert_one(result)
580
+
581
+ ptype = prize.get("type")
582
+ if ptype == "points":
583
+ await add_points(user_id, prize.get("value", 0), "mystery_box", f"جائزة صندوق {box_id}")
584
+ elif ptype == "xp":
585
+ await add_xp(user_id, prize.get("value", 0))
586
+ elif ptype == "ticket":
587
+ await db.users.update_one({"user_id": user_id}, {"$inc": {"tickets": prize.get("value", 1)}})
588
+ elif ptype == "coupon":
589
+ code = gen_code(6)
590
+ await db.promo_codes.insert_one(
591
+ {
592
+ "code": code,
593
+ "discount_percent": prize.get("value", 5),
594
+ "active": True,
595
+ "max_uses": 1,
596
+ "used_count": 0,
597
+ "owner_id": user_id,
598
+ "owner_points_reward": 0,
599
+ "created_at": now_utc(),
600
+ "expires_at": now_utc() + timedelta(days=30),
601
+ "allowed_users": [user_id],
602
+ }
603
+ )
604
+ prize = {**prize, "code": code}
605
+ return True, prize
606
+
607
+
608
+ # ---------------------------------------------------------------------------
609
+ # السحوبات (Giveaways)
610
+ # ---------------------------------------------------------------------------
611
+ async def join_giveaway(user_id: int, giveaway_id: str):
612
+ giveaway = await db.giveaways.find_one({"_id": giveaway_id, "status": "open"})
613
+ if not giveaway:
614
+ return False, "❌ هذا السحب غير متاح حالياً."
615
+ cost_tickets = giveaway.get("ticket_cost", 1)
616
+ charged = await db.users.find_one_and_update(
617
+ {"user_id": user_id, "tickets": {"$gte": cost_tickets}},
618
+ {"$inc": {"tickets": -cost_tickets}},
619
+ return_document=ReturnDocument.AFTER,
620
+ )
621
+ if not charged:
622
+ return False, "❌ لا تملك تذاكر كافية للمشاركة في هذا السحب."
623
+ try:
624
+ await db.giveaway_entries.insert_one(
625
+ {"giveaway_id": giveaway_id, "user_id": user_id, "tickets": cost_tickets, "joined_at": now_utc()}
626
+ )
627
+ except DuplicateKeyError:
628
+ await db.users.update_one({"user_id": user_id}, {"$inc": {"tickets": cost_tickets}})
629
+ return False, "❌ أنت مشارك بالفعل في هذا السحب."
630
+ return True, "✅ تم تسجيل مشاركتك في السحب بنجاح!"
631
+
632
+
633
+ async def draw_giveaway(giveaway_id: str, bot=None):
634
+ giveaway = await db.giveaways.find_one({"_id": giveaway_id})
635
+ if not giveaway or giveaway.get("status") != "open":
636
+ return None
637
+ entries = [e async for e in db.giveaway_entries.find({"giveaway_id": giveaway_id})]
638
+ if not entries:
639
+ await db.giveaways.update_one({"_id": giveaway_id}, {"$set": {"status": "closed_no_participants"}})
640
+ return None
641
+ winner = random.SystemRandom().choice(entries)
642
+ await db.giveaways.update_one(
643
+ {"_id": giveaway_id},
644
+ {"$set": {"status": "finished", "winner_id": winner["user_id"], "drawn_at": now_utc()}},
645
+ )
646
+ prize = giveaway.get("prize", {})
647
+ if prize.get("type") == "points":
648
+ await add_points(winner["user_id"], prize.get("value", 0), "giveaway_win", f"فوز بسحب {giveaway_id}")
649
+ if bot:
650
+ await safe_send(bot, winner["user_id"], f"🎉 مبروك! فزت بسحب: {giveaway.get('title')}")
651
+ return winner["user_id"]
652
+
653
+
654
+ # ---------------------------------------------------------------------------
655
+ # Leaderboard
656
+ # ---------------------------------------------------------------------------
657
+ async def get_leaderboard(limit: int = 10) -> List[Dict[str, Any]]:
658
+ cursor = db.users.find({"banned": {"$ne": True}}).sort("points", DESCENDING).limit(limit)
659
+ return [u async for u in cursor]
660
+
661
+
662
+ # ---------------------------------------------------------------------------
663
+ # Telegram Helpers
664
+ # ---------------------------------------------------------------------------
665
+ def main_menu_keyboard(is_admin: bool) -> ReplyKeyboardMarkup:
666
+ rows = [
667
+ [KeyboardButton("👤 حسابي"), KeyboardButton("🛒 المتجر")],
668
+ [KeyboardButton("🎁 المكافأة اليومية"), KeyboardButton("👥 الإحالات")],
669
+ [KeyboardButton("💎 VIP"), KeyboardButton("🎁 Mystery Box")],
670
+ [KeyboardButton("🎟️ السحوبات"), KeyboardButton("🏆 Leaderboard")],
671
+ [KeyboardButton("🏅 الإنجازات"), KeyboardButton("🏷️ أكوادي")],
672
+ [KeyboardButton("💰 بيع حساب مقابل نقاط")],
673
  ]
674
+ if is_admin:
675
+ rows.append([KeyboardButton("⚙️ لوحة المطورين")])
676
+ return ReplyKeyboardMarkup(rows, resize_keyboard=True)
677
+
678
+
679
+ async def rate_limited_interaction(user_id: int) -> bool:
680
+ settings = await get_settings()
681
+ cooldown = settings.get("points", {}).get("interaction_cooldown_seconds", 60)
682
+ last = LAST_SEEN.get(user_id)
683
+ if last and (now_utc() - last).total_seconds() < cooldown:
684
+ return False
685
+ LAST_SEEN[user_id] = now_utc()
686
+ return True
687
+
688
+
689
+ # ---------------------------------------------------------------------------
690
+ # Handlers — أوامر عامة
691
+ # ---------------------------------------------------------------------------
692
+ async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
693
+ tg_user = update.effective_user
694
+ try:
695
+ user = await get_or_create_user(tg_user.id, tg_user.username or "", tg_user.first_name or "")
696
+ except PyMongoError:
697
+ await update.message.reply_text("⚠️ تعذر الاتصال بقاعدة البيانات حالياً، حاول لاحقاً.")
698
+ return
699
+
700
+ if user.get("banned"):
701
+ await update.message.reply_text("🚫 حسابك محظور من استخدام هذا البوت.")
702
+ return
703
+
704
+ args = context.args
705
+ if args and args[0].startswith("ref_") and not user.get("referred_by"):
706
+ try:
707
+ referrer_id = int(args[0].replace("ref_", ""))
708
+ await process_referral(tg_user.id, referrer_id, context.bot)
709
+ except (ValueError, PyMongoError):
710
+ pass
711
+
712
+ settings = await get_settings()
713
+ admin = await has_permission(tg_user.id)
714
+ await update.message.reply_text(
715
+ settings.get("messages", {}).get("welcome", "أهلاً بك!"),
716
+ reply_markup=main_menu_keyboard(admin),
717
+ )
718
+
719
+
720
+ async def cmd_myid(update: Update, context: ContextTypes.DEFAULT_TYPE):
721
+ await update.message.reply_text(f"🆔 معرفك: `{update.effective_user.id}`", parse_mode=ParseMode.MARKDOWN)
722
+
723
+
724
+ async def show_profile(update: Update, context: ContextTypes.DEFAULT_TYPE):
725
+ uid = update.effective_user.id
726
+ user = await get_or_create_user(uid, update.effective_user.username or "", update.effective_user.first_name or "")
727
+ vip = user.get("vip")
728
+ vip_text = "لا يوجد" if not vip else f"{vip.get('plan_name','VIP')} (حتى {vip.get('expires_at')})"
729
+ text = (
730
+ f"👤 *حسابك*\n\n"
731
+ f"🪙 النقاط: {user.get('points',0)}\n"
732
+ f"⭐ XP: {user.get('xp',0)}\n"
733
+ f"📈 Level: {user.get('level',1)}\n"
734
+ f"💎 VIP: {vip_text}\n"
735
+ f"🎟️ التذاكر: {user.get('tickets',0)}\n"
736
+ f"🏅 الإنجازات: {len(user.get('achievements', []))}\n"
737
+ f"👥 الإحالات: {user.get('referrals_count',0)}\n"
738
+ f"🛒 المشتريات: {user.get('purchases_count',0)}\n"
739
+ f"💰 المبيعات: {user.get('sales_count',0)}\n"
740
+ )
741
+ await update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN)
742
+
743
+
744
+ async def show_store(update: Update, context: ContextTypes.DEFAULT_TYPE):
745
+ products = [p async for p in db.products.find({"status": "متوفر"}).limit(10)]
746
  if not products:
747
+ await update.message.reply_text("🛒 لا توجد حسابات متوفرة حالياً.")
 
748
  return
 
 
749
  for p in products:
750
+ price = p.get("price", 0)
751
+ flash = p.get("flash_sale")
752
+ caption = f"🎮 *{p.get('name')}*\n"
753
+ if flash and flash.get("expires_at", now_utc()) > now_utc() and flash.get("remaining", 0) > 0:
754
+ caption += f"💰 السعر: ~{price}~ 🔥 {flash.get('flash_price')}\n⏱️ ينتهي: {flash['expires_at'].strftime('%Y-%m-%d %H:%M UTC')}\n👤 المتبقي: {flash.get('remaining')}\n"
755
+ else:
756
+ caption += f"💰 السعر: {price} نقطة\n"
757
+ caption += f"📝 {p.get('description','')}\n"
758
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("🛒 شراء", callback_data=f"buy:{p['_id']}")]])
759
+ await update.message.reply_text(caption, parse_mode=ParseMode.MARKDOWN, reply_markup=kb)
760
+
761
+
762
+ async def cb_buy(update: Update, context: ContextTypes.DEFAULT_TYPE):
763
+ query = update.callback_query
764
+ await query.answer()
765
+ product_id = query.data.split(":", 1)[1]
766
+ ok, result = await purchase_product(query.from_user.id, product_id, context.bot)
767
+ if ok:
768
+ await query.message.reply_text(f"✅ تم شراء الحساب بنجاح: {result.get('name')}\nسيتواصل معك الأدمن لتسليم البيانات.")
769
+ else:
770
+ await query.message.reply_text(result)
771
+
772
+
773
+ async def show_sell(update: Update, context: ContextTypes.DEFAULT_TYPE):
774
+ uid = update.effective_user.id
775
+ await db.sell_requests.insert_one({"user_id": uid, "status": "pending", "created_at": now_utc()})
776
+ await db.users.update_one({"user_id": uid}, {"$inc": {"sales_count": 1}})
777
+ await check_achievements(uid, context.bot)
778
+ await update.message.reply_text(
779
+ f"💰 لبيع حسابك مقابل نقاط، تواصل ��باشرة مع: {SELL_ACCOUNT_CONTACT}\n"
780
+ "تم تسجيل طلبك في النظام."
781
+ )
782
+
783
+
784
+ async def show_daily(update: Update, context: ContextTypes.DEFAULT_TYPE):
785
+ uid = update.effective_user.id
786
+ user = await get_or_create_user(uid)
787
+ today = now_utc().date().isoformat()
788
+ if user.get("last_daily_claim") == today:
789
+ await update.message.reply_text("⏳ لقد استلمت المكافأة اليومية اليوم بالفعل، عد غداً!")
790
  return
791
+ settings = await get_settings()
792
+ reward = settings.get("points", {}).get("daily_reward", 0)
793
+ await add_points(uid, reward, "daily_reward", "المكافأة اليومية", reference_id=f"daily:{uid}:{today}")
794
+ await db.users.update_one({"user_id": uid}, {"$set": {"last_daily_claim": today}})
795
+ await update.message.reply_text(f"🎁 حصلت على {reward} نقطة كمكافأة يومية!")
796
+
797
+
798
+ async def show_referrals(update: Update, context: ContextTypes.DEFAULT_TYPE):
799
+ uid = update.effective_user.id
800
+ bot_username = (await context.bot.get_me()).username
801
+ link = f"https://t.me/{bot_username}?start=ref_{uid}"
802
+ user = await get_or_create_user(uid)
803
+ await update.message.reply_text(
804
+ f"👥 رابط الإحالة الخاص بك:\n{link}\n\nعدد إحالاتك: {user.get('referrals_count',0)}"
805
  )
806
+
807
+
808
+ async def show_vip(update: Update, context: ContextTypes.DEFAULT_TYPE):
809
+ plans = [p async for p in db.vip_plans.find({"enabled": {"$ne": False}})]
810
+ if not plans:
811
+ await update.message.reply_text("💎 لا توجد باقات VIP متاحة حالياً.")
 
 
 
 
 
 
 
 
 
812
  return
813
+ for plan in plans:
814
+ text = (
815
+ f"💎 *{plan.get('name')}*\n"
816
+ f"السعر: {plan.get('price')} نقطة | المدة: {plan.get('duration_days')} يوم\n"
817
+ f"المزايا: {plan.get('benefits_text','')}"
818
+ )
819
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("💎 اشتراك", callback_data=f"vip:{plan['_id']}")]])
820
+ await update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN, reply_markup=kb)
821
+
822
+
823
+ async def cb_vip(update: Update, context: ContextTypes.DEFAULT_TYPE):
824
+ query = update.callback_query
825
+ await query.answer()
826
+ plan_id = query.data.split(":", 1)[1]
827
+ plan = await db.vip_plans.find_one({"_id": plan_id})
828
+ if not plan:
829
+ await query.message.reply_text("❌ هذه الباقة لم تعد متاحة.")
830
+ return
831
+ price = plan.get("price", 0)
832
+ uid = query.from_user.id
833
+ charged = await db.users.find_one_and_update(
834
+ {"user_id": uid, "points": {"$gte": price}},
835
  {"$inc": {"points": -price}},
836
+ return_document=ReturnDocument.AFTER,
837
  )
838
+ if not charged:
839
+ await query.message.reply_text("❌ رصيدك غير كافٍ لهذه الباقة.")
 
840
  return
841
+ expires = now_utc() + timedelta(days=plan.get("duration_days", 30))
842
+ await db.users.update_one(
843
+ {"user_id": uid},
844
+ {"$set": {"vip": {"plan_id": plan_id, "plan_name": plan.get("name"), "expires_at": expires.isoformat()}}},
845
  )
846
+ await query.message.reply_text(f"✅ تم تفعيل {plan.get('name')} حتى {expires.strftime('%Y-%m-%d')}")
847
+
848
+
849
+ async def show_mystery(update: Update, context: ContextTypes.DEFAULT_TYPE):
850
+ settings = await get_settings()
851
+ if not settings.get("mystery_box_enabled", True):
852
+ await update.message.reply_text("🎁 Mystery Box غير مفعّل حالياً.")
853
+ return
854
+ boxes = [b async for b in db.mystery_boxes.find({"enabled": True})]
855
+ if not boxes:
856
+ await update.message.reply_text("🎁 لا توجد صناديق متاحة حالياً.")
857
+ return
858
+ for b in boxes:
859
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("🎁 افتح الصندوق", callback_data=f"box:{b['_id']}")]])
860
+ await update.message.reply_text(
861
+ f"🎁 *{b.get('name')}*\nالسعر: {b.get('price')} نقطة", parse_mode=ParseMode.MARKDOWN, reply_markup=kb
862
+ )
863
+
864
+
865
+ async def cb_box(update: Update, context: ContextTypes.DEFAULT_TYPE):
866
+ query = update.callback_query
867
+ await query.answer()
868
+ box_id = query.data.split(":", 1)[1]
869
+ ok, result = await open_mystery_box(query.from_user.id, box_id)
870
+ if not ok:
871
+ await query.message.reply_text(result)
872
+ return
873
+ ptype = result.get("type")
874
+ if ptype == "points":
875
+ txt = f"🎉 حصلت على {result.get('value')} نقطة!"
876
+ elif ptype == "xp":
877
+ txt = f"🎉 حصلت على {result.get('value')} XP!"
878
+ elif ptype == "ticket":
879
+ txt = f"🎉 حصلت على {result.get('value')} تذكرة/تذاكر سحب!"
880
+ elif ptype == "coupon":
881
+ txt = f"🎉 حصلت على كوبون خصم {result.get('value')}% — الكود: `{result.get('code')}`"
882
+ else:
883
+ txt = "🎉 حصلت على جائزة!"
884
+ await query.message.reply_text(txt, parse_mode=ParseMode.MARKDOWN)
885
+
886
+
887
+ async def show_giveaways(update: Update, context: ContextTypes.DEFAULT_TYPE):
888
+ settings = await get_settings()
889
+ if not settings.get("giveaways_enabled", True):
890
+ await update.message.reply_text("🎟️ السحوبات غير مفعّلة حالياً.")
891
+ return
892
+ giveaways = [g async for g in db.giveaways.find({"status": "open"})]
893
+ if not giveaways:
894
+ await update.message.reply_text("🎟️ لا توجد سحوبات مفتوحة حالياً.")
895
+ return
896
+ for g in giveaways:
897
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("🎟️ شارك الآن", callback_data=f"give:{g['_id']}")]])
898
+ await update.message.reply_text(
899
+ f"🎟️ *{g.get('title')}*\nتكلفة المشاركة: {g.get('ticket_cost',1)} تذكرة",
900
+ parse_mode=ParseMode.MARKDOWN, reply_markup=kb,
901
+ )
902
+
903
+
904
+ async def cb_giveaway_join(update: Update, context: ContextTypes.DEFAULT_TYPE):
905
+ query = update.callback_query
906
+ await query.answer()
907
+ gid = query.data.split(":", 1)[1]
908
+ ok, msg = await join_giveaway(query.from_user.id, gid)
909
+ await query.message.reply_text(msg)
910
+
911
+
912
+ async def show_leaderboard(update: Update, context: ContextTypes.DEFAULT_TYPE):
913
+ top = await get_leaderboard(10)
914
+ if not top:
915
+ await update.message.reply_text("🏆 لا توجد بيانات كافية بعد.")
916
  return
917
+ lines = ["🏆 *Top 10*\n"]
918
+ medals = ["🥇", "🥈", "🥉"]
919
+ for i, u in enumerate(top):
920
+ prefix = medals[i] if i < 3 else f"{i+1}."
921
+ name = u.get("username") or u.get("first_name") or str(u.get("user_id"))
922
+ lines.append(f"{prefix} {name} — {u.get('points',0)} نقطة")
923
+ await update.message.reply_text("\n".join(lines), parse_mode=ParseMode.MARKDOWN)
924
+
925
+
926
+ async def show_achievements(update: Update, context: ContextTypes.DEFAULT_TYPE):
927
+ uid = update.effective_user.id
928
+ user = await get_or_create_user(uid)
929
+ unlocked = set(user.get("achievements", []))
930
+ achs = [a async for a in db.achievements.find({})]
931
+ if not achs:
932
+ await update.message.reply_text("🏅 لا توجد إنجازات معرّفة بعد.")
933
+ return
934
+ lines = ["🏅 *الإنجازات*\n"]
935
+ for a in achs:
936
+ mark = "✅" if a["_id"] in unlocked else "🔒"
937
+ lines.append(f"{mark} {a.get('name')} — {a.get('description','')}")
938
+ await update.message.reply_text("\n".join(lines), parse_mode=ParseMode.MARKDOWN)
939
+
940
+
941
+ async def show_my_codes(update: Update, context: ContextTypes.DEFAULT_TYPE):
942
+ uid = update.effective_user.id
943
+ codes = [c async for c in db.promo_codes.find({"owner_id": uid})]
944
+ if not codes:
945
+ await update.message.reply_text("🏷️ لا تملك أكواد ��صم حالياً.")
946
+ return
947
+ lines = ["🏷️ *أكوادك*\n"]
948
+ for c in codes:
949
+ lines.append(f"`{c['code']}` — خصم {c.get('discount_percent')}% — استُخدم {c.get('used_count',0)}/{c.get('max_uses')}")
950
+ await update.message.reply_text("\n".join(lines), parse_mode=ParseMode.MARKDOWN)
951
+
952
+
953
+ async def on_group_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
954
+ if not update.effective_user or not update.message:
955
+ return
956
+ uid = update.effective_user.id
957
+ try:
958
+ await get_or_create_user(uid, update.effective_user.username or "", update.effective_user.first_name or "")
959
+ if not await rate_limited_interaction(uid):
960
+ return
961
+ settings = await get_settings()
962
+ pts = settings.get("points", {}).get("message_interaction", 0)
963
+ xp = settings.get("xp", {}).get("per_interaction", 0)
964
+ if pts:
965
+ await add_points(uid, pts, "interaction", "تفاعل داخل الكروب")
966
+ if xp:
967
+ await add_xp(uid, xp, context.bot)
968
+ await db.users.update_one({"user_id": uid}, {"$set": {"last_interaction_at": now_utc()}})
969
+ except PyMongoError:
970
+ logger.warning("تعذر تسجيل تفاعل المستخدم %s بسبب مشكلة اتصال DB", uid)
971
+
972
+
973
+ TEXT_ROUTES = {
974
+ "👤 حسابي": show_profile,
975
+ "🛒 المتجر": show_store,
976
+ "💰 بيع حساب مقابل نقاط": show_sell,
977
+ "🎁 المكافأة اليومية": show_daily,
978
+ "👥 الإحالات": show_referrals,
979
+ "💎 VIP": show_vip,
980
+ "🎁 Mystery Box": show_mystery,
981
+ "🎟️ السحوبات": show_giveaways,
982
+ "🏆 Leaderboard": show_leaderboard,
983
+ "🏅 الإنجازات": show_achievements,
984
+ "🏷️ أكوادي": show_my_codes,
985
+ }
986
+
987
+
988
+ async def on_private_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
989
+ text = update.message.text or ""
990
+ uid = update.effective_user.id
991
+
992
+ # أولاً: هل يوجد إدخال إداري معلّق بانتظار هذه الرسالة؟
993
+ if uid in PENDING:
994
+ await handle_pending_input(update, context)
995
+ return
996
+
997
+ if text == "⚙️ لوحة المطورين":
998
+ await admin_panel_root(update, context)
999
+ return
1000
+
1001
+ handler = TEXT_ROUTES.get(text)
1002
+ if handler:
1003
  try:
1004
+ await handler(update, context)
1005
+ except PyMongoError:
1006
+ await update.message.reply_text("⚠️ تعذر الاتصال بقاعدة البيانات، حاول مرة أخرى بعد قليل.")
1007
+ else:
1008
+ await update.message.reply_text("استخدم الأزرار في القائمة 👇")
1009
+
1010
+
1011
+ # ---------------------------------------------------------------------------
1012
+ # لوحة المطورين
1013
+ # ---------------------------------------------------------------------------
1014
+ ADMIN_SECTIONS = [
1015
+ ("users", "👥 المستخدمون"),
1016
+ ("store", "🛒 المتجر"),
1017
+ ("points", "🪙 إعدادات النقاط"),
1018
+ ("levels", "⭐ XP / Levels"),
1019
+ ("codes", "🏷️ أكواد الخصم"),
1020
+ ("vip", "💎 VIP"),
1021
+ ("boxes", "🎁 Mystery Boxes"),
1022
+ ("giveaways", "🎟️ السحوبات"),
1023
+ ("achievements", "🏅 الإنجازات"),
1024
+ ("settings", "⚙️ إعدادات عامة"),
1025
+ ("admins", "👮 الأدمنية"),
1026
+ ("stats", "📊 الإحصائيات"),
1027
+ ("audit", "🧾 Audit Log"),
1028
+ ]
1029
+
1030
+
1031
+ def admin_root_keyboard() -> InlineKeyboardMarkup:
1032
+ buttons = [[InlineKeyboardButton(label, callback_data=f"adm:{key}:root")] for key, label in ADMIN_SECTIONS]
1033
+ return InlineKeyboardMarkup(buttons)
1034
+
1035
+
1036
+ async def admin_panel_root(update: Update, context: ContextTypes.DEFAULT_TYPE):
1037
+ uid = update.effective_user.id
1038
+ if not await has_permission(uid):
1039
+ await update.message.reply_text("🚫 ليست لديك صلاحية الوصول للوحة المطورين.")
1040
+ return
1041
+ await update.message.reply_text("⚙️ *لوحة المطورين*\nاختر قسماً:", parse_mode=ParseMode.MARKDOWN, reply_markup=admin_root_keyboard())
1042
+
1043
+
1044
+ async def cmd_admin(update: Update, context: ContextTypes.DEFAULT_TYPE):
1045
+ await admin_panel_root(update, context)
1046
+
1047
+
1048
+ def require_roles(section: str) -> List[str]:
1049
+ mapping = {
1050
+ "users": ["user_admin"],
1051
+ "store": ["store_admin"],
1052
+ "points": ["points_admin"],
1053
+ "levels": ["points_admin"],
1054
+ "codes": ["points_admin", "store_admin"],
1055
+ "vip": ["vip_admin"],
1056
+ "boxes": ["store_admin", "points_admin"],
1057
+ "giveaways": ["giveaway_admin"],
1058
+ "achievements": ["points_admin"],
1059
+ "settings": ["super_admin"],
1060
+ "admins": ["super_admin"],
1061
+ "stats": [],
1062
+ "audit": ["super_admin"],
1063
+ }
1064
+ return mapping.get(section, [])
1065
+
1066
+
1067
+ async def cb_admin(update: Update, context: ContextTypes.DEFAULT_TYPE):
1068
+ query = update.callback_query
1069
+ await query.answer()
1070
+ uid = query.from_user.id
1071
+ parts = query.data.split(":")
1072
+ _, section, action, *rest = parts + [""] * (3 - len(parts) + 1)
1073
+
1074
+ if not await has_permission(uid, require_roles(section)):
1075
+ await query.message.reply_text("🚫 ليست لديك صلاحية لهذا القسم.")
1076
+ return
1077
+
1078
+ if section == "users":
1079
+ await admin_users_menu(query, context, action, rest)
1080
+ elif section == "store":
1081
+ await admin_store_menu(query, context, action, rest)
1082
+ elif section == "points":
1083
+ await admin_json_section_menu(query, context, "points", "إعدادات النقاط")
1084
+ elif section == "levels":
1085
+ await admin_json_section_menu(query, context, "levels", "قائمة المستويات (XP/Levels)")
1086
+ elif section == "codes":
1087
+ await admin_list_menu(query, context, db.promo_codes, "codes", "أكواد الخصم", "code")
1088
+ elif section == "vip":
1089
+ await admin_list_menu(query, context, db.vip_plans, "vip", "باقات VIP", "name")
1090
+ elif section == "boxes":
1091
+ await admin_list_menu(query, context, db.mystery_boxes, "boxes", "Mystery Boxes", "name")
1092
+ elif section == "giveaways":
1093
+ await admin_giveaways_menu(query, context, action, rest)
1094
+ elif section == "achievements":
1095
+ await admin_list_menu(query, context, db.achievements, "achievements", "الإنجازات", "name")
1096
+ elif section == "settings":
1097
+ await admin_json_section_menu(query, context, "links", "روابط وإعدادات عامة")
1098
+ elif section == "admins":
1099
+ await admin_admins_menu(query, context, action, rest)
1100
+ elif section == "stats":
1101
+ await admin_stats(query, context)
1102
+ elif section == "audit":
1103
+ await admin_audit(query, context)
1104
+
1105
+
1106
+ # --- قسم المستخدمين ---
1107
+ async def admin_users_menu(query, context, action, rest):
1108
+ if action == "root":
1109
+ PENDING[query.from_user.id] = {"flow": "find_user"}
1110
+ await query.message.reply_text("🔎 أرسل الآن User ID للمستخدم الذي تريد إدارته:")
1111
+ return
1112
+ target_id = int(rest[0]) if rest and rest[0] else None
1113
+ if action == "view" and target_id:
1114
+ await send_user_admin_card(query.message, target_id)
1115
+ elif action in ("addpts", "subpts", "addxp") and target_id:
1116
+ PENDING[query.from_user.id] = {"flow": "user_amount", "action": action, "target": target_id}
1117
+ await query.message.reply_text("✏️ أرسل القيمة (رقم):")
1118
+ elif action == "ban" and target_id:
1119
+ await db.users.update_one({"user_id": target_id}, {"$set": {"banned": True}})
1120
+ await audit(query.from_user.id, "ban_user", {"target": target_id})
1121
+ await query.message.reply_text("🚫 تم حظر المستخدم.")
1122
+ elif action == "unban" and target_id:
1123
+ await db.users.update_one({"user_id": target_id}, {"$set": {"banned": False}})
1124
+ await audit(query.from_user.id, "unban_user", {"target": target_id})
1125
+ await query.message.reply_text("✅ تم فك الحظر.")
1126
+ elif action == "freeze" and target_id:
1127
+ await db.users.update_one({"user_id": target_id}, {"$set": {"frozen": True}})
1128
+ await audit(query.from_user.id, "freeze_user", {"target": target_id})
1129
+ await query.message.reply_text("🧊 تم تجميد الحساب.")
1130
+ elif action == "unfreeze" and target_id:
1131
+ await db.users.update_one({"user_id": target_id}, {"$set": {"frozen": False}})
1132
+ await audit(query.from_user.id, "unfreeze_user", {"target": target_id})
1133
+ await query.message.reply_text("✅ تم إلغاء التجميد.")
1134
+
1135
+
1136
+ async def send_user_admin_card(message, target_id: int):
1137
+ user = await db.users.find_one({"user_id": target_id})
1138
+ if not user:
1139
+ await message.reply_text("❌ لم يتم العثور على هذا المستخدم.")
1140
+ return
1141
  text = (
1142
+ f"👤 المستخدم {target_id}\n"
1143
+ f"النقاط: {user.get('points')} | XP: {user.get('xp')} | Level: {user.get('level')}\n"
1144
+ f"محظور: {user.get('banned')} | مجمّد: {user.get('frozen')}\n"
1145
+ f"إحالات: {user.get('referrals_count')} | مشتريات: {user.get('purchases_count')} | مبيعات: {user.get('sales_count')}"
1146
  )
1147
+ kb = InlineKeyboardMarkup(
1148
+ [
1149
+ [
1150
+ InlineKeyboardButton("➕ نقاط", callback_data=f"adm:users:addpts:{target_id}"),
1151
+ InlineKeyboardButton("➖ نقاط", callback_data=f"adm:users:subpts:{target_id}"),
1152
+ InlineKeyboardButton("➕ XP", callback_data=f"adm:users:addxp:{target_id}"),
1153
+ ],
1154
+ [
1155
+ InlineKeyboardButton("🚫 حظر", callback_data=f"adm:users:ban:{target_id}"),
1156
+ InlineKeyboardButton("✅ فك حظر", callback_data=f"adm:users:unban:{target_id}"),
1157
+ ],
1158
+ [
1159
+ InlineKeyboardButton("🧊 تجميد", callback_data=f"adm:users:freeze:{target_id}"),
1160
+ InlineKeyboardButton("🔓 إلغاء تجميد", callback_data=f"adm:users:unfreeze:{target_id}"),
1161
+ ],
1162
+ ]
1163
+ )
1164
+ await message.reply_text(text, reply_markup=kb)
1165
+
1166
+
1167
+ # --- قسم المتجر ---
1168
+ async def admin_store_menu(query, context, action, rest):
1169
+ if action == "root":
1170
+ products = [p async for p in db.products.find({}).limit(20)]
1171
+ buttons = [[InlineKeyboardButton(f"{p.get('name')} ({p.get('status')})", callback_data=f"adm:store:view:{p['_id']}")] for p in products]
1172
+ buttons.append([InlineKeyboardButton("➕ إضافة منتج (JSON)", callback_data="adm:store:add:")])
1173
+ await query.message.reply_text("🛒 المنتجات:", reply_markup=InlineKeyboardMarkup(buttons))
1174
+ elif action == "add":
1175
+ PENDING[query.from_user.id] = {"flow": "store_add"}
1176
+ await query.message.reply_text(
1177
+ "أرسل بيانات المنتج بصيغة JSON، مثال:\n"
1178
+ '`{"_id":"acc1","name":"حساب VIP","price":20000,"description":"وصف","status":"متوفر"}`',
1179
+ parse_mode=ParseMode.MARKDOWN,
1180
+ )
1181
+ elif action == "view" and rest and rest[0]:
1182
+ pid = rest[0]
1183
+ p = await db.products.find_one({"_id": pid})
1184
+ if not p:
1185
+ await query.message.reply_text("❌ غير موجود.")
1186
+ return
1187
+ kb = InlineKeyboardMarkup(
1188
+ [
1189
+ [InlineKeyboardButton("✏️ تعديل (JSON)", callback_data=f"adm:store:edit:{pid}")],
1190
+ [InlineKeyboardButton("🔥 Flash Sale", callback_data=f"adm:store:flash:{pid}")],
1191
+ [InlineKeyboardButton("🗑️ حذف", callback_data=f"adm:store:del:{pid}")],
1192
+ ]
1193
+ )
1194
+ await query.message.reply_text(f"```{json.dumps(p, ensure_ascii=False, default=str, indent=2)}```", parse_mode=ParseMode.MARKDOWN, reply_markup=kb)
1195
+ elif action == "edit" and rest and rest[0]:
1196
+ PENDING[query.from_user.id] = {"flow": "store_edit", "id": rest[0]}
1197
+ await query.message.reply_text("أرسل الحقول المراد تعديلها بصيغة JSON (سيتم دمجها مع المنتج الحالي):")
1198
+ elif action == "flash" and rest and rest[0]:
1199
+ PENDING[query.from_user.id] = {"flow": "store_flash", "id": rest[0]}
1200
+ await query.message.reply_text('أرسل بيانات العرض JSON، مثال:\n`{"flash_price":15000,"minutes":120,"remaining":1}`', parse_mode=ParseMode.MARKDOWN)
1201
+ elif action == "del" and rest and rest[0]:
1202
+ await db.products.delete_one({"_id": rest[0]})
1203
+ await audit(query.from_user.id, "delete_product", {"id": rest[0]})
1204
+ await query.message.reply_text("🗑️ تم الحذف.")
1205
+
1206
+
1207
+ # --- قسم عام: تعديل settings.<section> عبر JSON ---
1208
+ async def admin_json_section_menu(query, context, section, label):
1209
+ settings = await get_settings()
1210
+ current = settings.get(section)
1211
+ PENDING[query.from_user.id] = {"flow": "settings_json", "section": section}
1212
+ await query.message.reply_text(
1213
+ f"⚙️ *{label}* — القيمة الحالية:\n```{json.dumps(current, ensure_ascii=False, default=str, indent=2)}```\n\n"
1214
+ "أرسل القيمة الجديدة بصيغة JSON لتحديثها:",
1215
+ parse_mode=ParseMode.MARKDOWN,
1216
+ )
1217
+
1218
+
1219
+ # --- قسم عام: عرض/إضافة عناصر مجموعة (codes/vip/boxes/achievements) ---
1220
+ async def admin_list_menu(query, context, collection, section, label, name_field):
1221
+ items = [i async for i in collection.find({}).limit(20)]
1222
+ buttons = [[InlineKeyboardButton(str(i.get(name_field, i.get("_id"))), callback_data=f"adm:{section}:view:{i['_id']}")] for i in items]
1223
+ buttons.