max1949 commited on
Commit
9933f70
·
verified ·
1 Parent(s): 2f20eff

Rename main.py to app.py

Browse files
Files changed (1) hide show
  1. main.py → app.py +176 -371
main.py → app.py RENAMED
@@ -15,71 +15,45 @@ import sys
15
  import traceback
16
  import io
17
  import time
 
18
  from PIL import Image, ImageDraw, ImageFont
19
- from flask import Flask
20
- from threading import Thread
21
 
22
  # ==============================================================================
23
- # 0. 机构级审计日志 (Hugging Face Optimized Logging)
24
  # ==============================================================================
25
  logging.basicConfig(
26
  level=logging.INFO,
27
  format='%(asctime)s [%(levelname)s] %(message)s',
28
  handlers=[logging.StreamHandler(sys.stdout)]
29
  )
30
- logger = logging.getLogger("BK_GTA_ARCHITECT_HF")
31
 
32
  # ==============================================================================
33
- # 1. 物理层适配:Hugging Face 端口映射 (Port 7860)
34
  # ==============================================================================
35
- app = Flask(__name__)
36
-
37
- @app.route('/')
38
- def home():
39
  """
40
- HF Spaces 健康检查探针。
41
- 返回 200 OK 以告知 HF 负载均衡器容器存活。
42
- 注入 bot.latency 以确保监控访问能强制唤醒 Discord 线程。
43
  """
44
  now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
45
  latency = "N/A"
46
  try:
47
- # 核心逻辑:通过读取 bot 属性,强制 Python 解释器保持 Discord 线程活跃
48
- if bot.is_ready():
49
  latency = f"{round(bot.latency * 1000, 2)}ms"
50
- except Exception as e:
51
- logger.warning(f"Heartbeat probe warning: {e}")
52
 
53
  return {
54
- "status": "BK-GTA-HF-TITAN-ACTIVE",
55
- "platform": "Hugging Face Docker Node",
56
- "engine": "V62.9-Titan",
57
  "bot_latency": latency,
58
  "server_time": now,
59
- "sentinel": "PROTECTED",
60
- "memory_pool": f"{sys.getsizeof(bk_engine.data) / 1024:.2f} KB"
61
  }
62
 
63
- def run_web_server():
64
- """
65
- [CRITICAL] Hugging Face 强制要求监听 7860 端口。
66
- 这是外部 UptimeRobot 能够触达容器的唯一物理入口。
67
- """
68
- try:
69
- logger.info("📡 [Sentinel] Starting Web Server on Port 7860...")
70
- import logging
71
- log = logging.getLogger('werkzeug')
72
- log.setLevel(logging.ERROR)
73
- app.run(host='0.0.0.0', port=7860, threaded=True)
74
- except Exception as e:
75
- logger.error(f"⚠️ [Sentinel] Web Server Failure: {e}")
76
-
77
- def keep_alive():
78
- """开启独立守护线程运行 Web 服务"""
79
- t = Thread(target=run_web_server)
80
- t.daemon = True
81
- t.start()
82
-
83
  # ==============================================================================
84
  # 2. 密钥池与环境配置 (Key Rotation Engine)
85
  # ==============================================================================
@@ -87,7 +61,6 @@ DISCORD_TOKEN = os.environ.get('DISCORD_TOKEN')
87
  SESSION_ID = random.randint(10000, 99999)
88
 
89
  # [CORE] 8 密钥轮询加载器
90
- # 在 HF Secrets 中设置 GEMINI_KEYS_POOL = key1,key2,key3...
91
  raw_keys = os.environ.get('GEMINI_KEYS_POOL', '')
92
  KEY_POOL = []
93
 
@@ -114,31 +87,27 @@ intents.message_content = True
114
  intents.members = True
115
  bot = commands.Bot(command_prefix='!', intents=intents)
116
 
117
- # 并发审计锁 (防止 OOM)
118
  audit_lock = asyncio.Lock()
119
 
120
- # 数据库路径适配 (优先使用 HF 持久化目录 /data)
121
  if os.path.exists('/data'):
122
  DB_PATH_CONFIG = '/data/bk_gta_v62_unified.json'
123
  logger.info(f"📂 [Storage] Using Persistent Storage: {DB_PATH_CONFIG}")
124
  else:
125
  DB_PATH_CONFIG = 'bk_gta_v62_unified.json'
126
- logger.warning(f"⚠️ [Storage] Using Ephemeral Storage (Data lost on restart): {DB_PATH_CONFIG}")
127
 
128
  # ==============================================================================
129
- # 3. 状态引擎与持久化数据库 (BKEngine HF Adapter)
130
  # ==============================================================================
131
  class BKEngine:
132
- """
133
- 核心数据引擎:负责量化资产持久化、状态树管理及风险指标对账
134
- """
135
  def __init__(self):
136
  self.db_file = DB_PATH_CONFIG
137
  self.data = self._load_db()
138
  self.pending_challenges = {}
139
  self.active_duels = {}
140
  self.active_bets = {}
141
- self.system_uptime = datetime.datetime.now()
142
 
143
  def _load_db(self):
144
  if os.path.exists(self.db_file):
@@ -154,7 +123,7 @@ class BKEngine:
154
 
155
  def _init_schema(self):
156
  return {
157
- "version": "62.9-HF-Titan",
158
  "users": {},
159
  "factions": {
160
  "BULLS": {"score": 0, "emoji": "🐂", "total_wins": 0},
@@ -166,7 +135,6 @@ class BKEngine:
166
  }
167
 
168
  def save_db(self):
169
- """执行物理持久化,确保数据资产安全"""
170
  try:
171
  # 原子写入防止文件损坏
172
  temp = self.db_file + '.tmp'
@@ -189,7 +157,6 @@ class BKEngine:
189
  "win_streak": 0,
190
  "mdd_record": 0.0,
191
  "sharpe_record": 0.0,
192
- "performance_log": [],
193
  "last_active": str(datetime.datetime.now())
194
  }
195
  return self.data["users"][uid]
@@ -198,144 +165,104 @@ class BKEngine:
198
  u = self.get_user(user_id)
199
  u["audits_count"] += 1
200
  u["total_score"] += score
201
- if score > u.get("highest_score", 0):
202
- u["highest_score"] = score
203
  if mdd is not None: u["mdd_record"] = mdd
204
  if sharpe is not None: u["sharpe_record"] = sharpe
205
-
206
- # 奖励计算:审计积分奖励
207
  u["points"] += 50
208
  u["last_active"] = str(datetime.datetime.now())
209
- u["performance_log"].append({"score": score, "time": str(datetime.datetime.now())})
210
  self.save_db()
211
 
212
  def update_elo(self, winner_id, loser_id):
213
- """
214
- 基于 ELO 等级分系统的量化能力更新算法
215
- K = 32 (常数项)
216
- """
217
- w = self.get_user(winner_id)
218
- l = self.get_user(loser_id)
219
-
220
  K = 32
221
  Rw, Rl = w["elo"], l["elo"]
222
  Ew = 1 / (1 + 10 ** ((Rl - Rw) / 400))
223
-
224
  w["elo"] = round(Rw + K * (1 - Ew))
225
  l["elo"] = round(Rl + K * (0 - Ew))
226
-
227
  w["win_streak"] += 1
228
  l["win_streak"] = 0
229
-
230
- # 阵营荣誉点累加
231
- if w["faction"] and w["faction"] in self.data["factions"]:
232
  self.data["factions"][w["faction"]]["score"] += 15
233
  self.data["factions"][w["faction"]]["total_wins"] += 1
234
-
235
  self.save_db()
236
  return w["elo"], l["elo"], w["win_streak"]
237
 
238
  bk_engine = BKEngine()
239
 
240
  # ==============================================================================
241
- # 4. 辅助内核与密钥轮询 (Multi-Key Multiplexer)
242
  # ==============================================================================
243
  def safe_extract_float(text, key):
244
- """鲁棒性数值提取引擎,支持多重前缀匹配"""
245
  try:
246
  patterns = [
247
- rf"{key}:\s*([-+]?\d*\.?\d+)",
248
  rf"{key}\s*=\s*([-+]?\d*\.?\d+)",
249
  rf"【{key}】\s*([-+]?\d*\.?\d+)"
250
  ]
251
- for pattern in patterns:
252
- match = re.search(pattern, text, re.IGNORECASE)
253
  if match: return float(match.group(1))
254
  return 0.0
255
- except Exception: return 0.0
256
 
257
  def safe_extract_text(text, key_start, key_end=None):
258
- """逻辑块提取引擎,支持长文本切片"""
259
  try:
260
- if key_end:
261
- pattern = rf"{key_start}:(.*?){key_end}:"
262
- else:
263
- pattern = rf"{key_start}:(.*)"
264
- match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
265
  if match:
266
- content = match.group(1).strip()
267
- # 过滤 Markdown 字符防止渲染崩溃
268
- return content.replace('*', '').replace('`', '').strip()
269
  return "N/A"
270
- except Exception: return "N/A"
271
 
272
  async def call_gemini_multiplex(prompt, attachment=None):
273
  """
274
  [Core Logic] 8 密钥自动轮询与故障转移
275
- 异步多密钥池轮询请求核心 | 具备 429 限流自动熔断机制
276
  """
277
- if not KEY_POOL:
278
- logger.error("Request Blocked: No API Keys available.")
279
- return "ERROR: NO_KEYS_CONFIGURED"
280
 
281
  image_data = None
282
  if attachment:
283
- try:
284
- image_data = await attachment.read()
285
- except Exception as e:
286
- logger.error(f"Attachment Read Error: {e}")
287
- return "ERROR: IMG_READ_FAIL"
288
 
289
  loop = asyncio.get_running_loop()
290
-
291
- # 随机打乱密钥顺序以实现负载均衡
292
  shuffled_keys = list(KEY_POOL)
293
  random.shuffle(shuffled_keys)
294
 
295
  for i, api_key in enumerate(shuffled_keys):
296
  try:
297
- # 实例化客户端
298
  client = genai.Client(api_key=api_key)
299
- req_contents = [prompt]
300
  if image_data:
301
- req_contents.append(types.Part.from_bytes(data=image_data, mime_type='image/png'))
302
 
303
- # 使用线程池执行同步 SDK 调用以防阻塞事件循环
304
  response = await loop.run_in_executor(
305
  None,
306
- functools.partial(client.models.generate_content, model="gemini-1.5-flash", contents=req_contents)
307
  )
308
 
309
- if response and response.text:
310
- return response.text
311
 
312
  except Exception as e:
313
  err_msg = str(e)
314
  if "429" in err_msg:
315
- logger.warning(f"⚠️ Key {i+1}/{len(KEY_POOL)} Rate Limited (429). Switching...")
316
- continue # 立即尝试下一个密钥
317
- elif "400" in err_msg: # 图片格式错误等
318
- logger.error(f"❌ Bad Request: {err_msg}")
319
- return "ERROR: BAD_REQUEST"
320
- else:
321
- logger.error(f"🤖 Gemini Error on Key {i+1}: {err_msg}")
322
- continue # 尝试下一个
323
 
324
  return "ERROR: ALL_KEYS_EXHAUSTED"
325
 
326
  # ==============================================================================
327
- # 5. 模块一:全网天梯与个人档案 (Rank & Profile)
328
  # ==============================================================================
329
  @bot.command()
330
  async def profile(ctx, member: discord.Member = None):
331
- """
332
- 展现量化交易员的详细画像与 ELO 评级
333
- """
334
  target = member or ctx.author
335
  u = bk_engine.get_user(target.id)
336
-
337
- # 动态段位逻辑展开
338
  elo = u['elo']
 
 
339
  if elo < 1300: rank, color = "Iron (学徒) 🗑️", 0x717d7e
340
  elif elo < 1500: rank, color = "Bronze (资深) 🥉", 0xcd7f32
341
  elif elo < 1800: rank, color = "Silver (精英) 🥈", 0xbdc3c7
@@ -346,98 +273,65 @@ async def profile(ctx, member: discord.Member = None):
346
  emb = discord.Embed(title=f"📊 Quantitative Profile: {target.display_name}", color=color)
347
  emb.add_field(name="Rating (ELO)", value=f"**{elo}**\nRank: `{rank}`", inline=True)
348
  emb.add_field(name="Assets (Points)", value=f"💰 **{u['points']}**", inline=True)
 
 
 
 
349
 
350
- f_name = u['faction']
351
- f_emoji = bk_engine.data["factions"].get(f_name, {}).get("emoji", "🌐") if f_name else "Ronin"
352
- emb.add_field(name="Faction", value=f"{f_emoji} {f_name or 'Unassigned'}", inline=True)
353
-
354
- # 展开性能统计指标
355
- perf_stats = (
356
  f"Total Audits: {u['audits_count']}\n"
357
- f"Peak Audit Score: {u['highest_score']:.2f}\n"
358
- f"Historical MDD: {u.get('mdd_record', 0):.2f}%\n"
359
- f"Max Win Streak: {u['win_streak']}"
360
  )
361
- emb.add_field(name="Risk-Adjusted Performance", value=f"```yaml\n{perf_stats}\n```", inline=False)
362
-
363
- if target.avatar:
364
- emb.set_thumbnail(url=target.avatar.url)
365
 
366
- emb.set_footer(text=f"System Session: {SESSION_ID} | Node: HuggingFace")
 
367
  await ctx.send(embed=emb)
368
 
369
  @bot.command()
370
  async def leaderboard(ctx):
371
- """
372
- 全量天梯对账,展示排名前 10 的顶级交易员
373
- """
374
- all_users = []
375
- for uid, data in bk_engine.data["users"].items():
376
- all_users.append({"id": uid, "elo": data["elo"], "score": data["highest_score"]})
377
-
378
- # 按 ELO 排序
379
- top_elo = sorted(all_users, key=lambda x: x["elo"], reverse=True)[:10]
380
-
381
  desc = ""
382
- for idx, u in enumerate(top_elo):
383
- medal = "🥇" if idx == 0 else "🥈" if idx == 1 else "🥉" if idx == 2 else f"{idx+1}."
384
- desc += f"{medal} <@{u['id']}> : ELO **{u['elo']}** | Peak Score: **{u['score']:.1f}**\n"
385
-
386
- emb = discord.Embed(
387
- title="🏆 BK-GTA Institutional Leaderboard",
388
- description=desc or "Scanning market data... (Waiting for submissions)",
389
- color=0xf1c40f
390
- )
 
 
391
 
392
- # 加入阵营战统计
393
- f_info = ""
394
- for name, info in bk_engine.data["factions"].items():
395
- f_info += f"{info['emoji']} {name}: {info['score']} pts "
396
- if f_info:
397
- emb.add_field(name="⚔️ Faction War Status", value=f_info, inline=False)
398
-
399
  await ctx.send(embed=emb)
400
 
401
  # ==============================================================================
402
- # 6. 模块二:宏观情报分析 (Macro Intelligence Engine)
403
  # ==============================================================================
404
  @bot.command()
405
  async def news(ctx):
406
- """
407
- 分析新闻/报表截图,并基于机构逻辑给出宏观评分
408
- """
409
- if not ctx.message.attachments:
410
- return await ctx.send("❌ Audit Refused: 请上传新闻图表。")
411
-
412
- m = await ctx.send("📡 **[HF-Node] Parsing Macro Pulse...**")
413
-
414
  prompt = (
415
- "你是一名对冲基金首席策略师。请解析此截图并输出:\n"
416
- "1. 情绪评分 (SCORE): -10 (极度利空) +10 (极度利多)。\n"
417
- "2. 核心变量 (SUMMARY): 30字内概括。\n"
418
- "3. 隐含逻辑 (TAKE): 机构视角的解读 (100字内)。\n"
419
- "输出格式必须严格如下:\n"
420
- "SCORE: [分值]\n"
421
- "SUMMARY: [内容]\n"
422
- "TAKE: [解读]"
423
  )
424
-
425
  res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
426
 
427
- if "ERROR" in res:
428
- await m.delete()
429
- return await ctx.send("🔴 [Audit Failure] Intelligence node offline.")
430
-
431
  score = safe_extract_float(res, "SCORE")
432
  summary = safe_extract_text(res, "SUMMARY", "TAKE")
433
  take = safe_extract_text(res, "TAKE")
434
-
435
- color = 0x2ecc71 if score > 0 else 0xe74c3c if score < 0 else 0xbdc3c7
436
- emb = discord.Embed(title="📰 Macro Sentiment Pulse", color=color)
437
- emb.add_field(name="Sentiment Score", value=f"**{score:+.1f} / 10**", inline=True)
438
- emb.add_field(name="Core Catalyst", value=summary, inline=False)
439
- emb.add_field(name="🏦 Institutional Analysis", value=f"```\n{take}\n```", inline=False)
440
-
441
  await m.delete()
442
  await ctx.send(embed=emb)
443
 
@@ -447,39 +341,28 @@ async def news(ctx):
447
  @bot.command()
448
  async def evaluate(ctx):
449
  """
450
- 基于截图全维度识别回测/实盘参数,并执行风控打分。
451
- 带有并发锁 (Semaphore) 防止 HF 容器 OOM。
452
  """
453
- if not ctx.message.attachments:
454
- return await ctx.send("❌ Audit Refused: 请上传结算单或净值曲线。")
455
-
456
  # [Lock Check] 检查是否忙碌
457
  if audit_lock.locked():
458
- return await ctx.send("⚠️ **[System Busy]** Another audit is processing. Please wait 10s to prevent memory overflow.")
459
 
460
  async with audit_lock:
461
- sid = random.randint(1000, 9999)
462
- m = await ctx.send(f"🛡️ **[SID:{sid}] Audit Engine Initializing... (Locked Mode)**")
463
-
464
- # 深度审计 Prompt
465
  prompt = (
466
- "你是一名顶级量化风险官。请执行全维度审计。\n"
467
- "【提取要求】:\n"
468
- "1. 核心指标:RET(收益率), MDD(最大回撤), PF(盈利因子), SHARPE(夏普), TRADES(笔数)。\n"
469
- "2. 亮点 (H_ZH): 中文优势概括。\n"
470
- "3. 评分 (S_BASE): 基础评分 (0-100)。\n"
471
- "4. 否决项 (VIOLATION): MDD > 35% 或 交易笔数 < 10 时为 TRUE。\n"
472
- "输出格式:\n"
473
- "RET: [值]\nMDD: [值]\nPF: [值]\nSHARPE: [值]\nTRADES: [笔数]\nS_BASE: [分]\nVIOLATION: [TRUE/FALSE]\nH_ZH: [优势]"
474
  )
475
-
476
  res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
477
-
478
  if "ERROR" in res:
479
  await m.delete()
480
- return await ctx.send("🔴 [Audit Crash] Engine disconnected.")
481
 
482
- # 提取数值
483
  ret = safe_extract_float(res, "RET")
484
  mdd = safe_extract_float(res, "MDD")
485
  pf = safe_extract_float(res, "PF")
@@ -488,199 +371,134 @@ async def evaluate(ctx):
488
  s_base = safe_extract_float(res, "S_BASE")
489
  violation = "TRUE" in res.upper()
490
 
491
- # 逻辑加权算法展开:S = S_base * (log10(Trades)/1.5) * Sharpe_Factor
492
- # 如果 MDD > 35%,执行风控一票否决
493
  if violation or mdd > 35.0:
494
  s_final = 0.0
495
- status = "⚠️ REJECTED (High Risk)"
496
  color = 0xe74c3c
497
  else:
 
498
  trades_adj = math.log10(max(trades, 1)) / 1.5
499
- s_final = s_base * min(max(trades_adj, 0.5), 1.2) * (sharpe / 2.0 if sharpe > 0 else 0.5)
500
  s_final = min(max(s_final, 0.0), 100.0)
501
- status = "✅ AUDIT CLEARED"
502
  color = 0x2ecc71
503
 
504
- # 持久化
505
  bk_engine.update_audit_stats(ctx.author.id, s_final, mdd, sharpe)
506
- h_zh = safe_extract_text(res, "H_ZH")
507
-
508
- emb = discord.Embed(title="🛡️ BK-GTA Gene Audit Report", color=color)
509
- emb.add_field(name="Verdict", value=f"**{status}**", inline=False)
510
 
511
- metrics = (
512
- f"💰 Return: {ret}% | MDD: {mdd}%\n"
513
- f"⚖️ PF: {pf} | Sharpe: {sharpe}\n"
514
- f"🎯 Sample Size: {trades} trades"
515
- )
516
- emb.add_field(name="Data Extraction", value=f"```\n{metrics}\n```", inline=False)
517
- emb.add_field(name="Analysis Score", value=f"**{s_final:.2f} / 100**", inline=True)
518
- emb.add_field(name="Highlights", value=h_zh, inline=False)
519
 
520
- emb.set_footer(text=f"SID: {sid} | Assets +50")
521
  await m.delete()
522
  await ctx.send(embed=emb)
523
 
524
  # ==============================================================================
525
- # 8. 模块四:博弈盘口 (Institutional Betting System)
526
  # ==============================================================================
527
  @bot.command()
528
  @commands.has_permissions(administrator=True)
529
- async def open_bet(ctx, *, topic: str):
530
- """
531
- 由合伙人开启的宏观/行情博弈盘口
532
- """
533
  bid = str(random.randint(100, 999))
534
  bk_engine.active_bets[bid] = {
535
- "topic": topic,
536
- "up": 0, "down": 0,
537
- "u_up": {}, "u_down": {},
538
  "status": "OPEN",
539
  "start_time": str(datetime.datetime.now())
540
  }
541
- await ctx.send(f"🎰 **[Bet-ID:{bid}]** New Market Entry!\nTopic: **{topic}**\nJoin: `!bet {bid} UP/DOWN [Amt]`")
542
 
543
  @bot.command()
544
  async def bet(ctx, bid: str, side: str, amt: int):
545
- """
546
- 交易员消耗积分参与盘口对冲
547
- """
548
- if bid not in bk_engine.active_bets:
549
- return await ctx.send("❌ Error: Invalid Bet ID.")
550
-
551
- b = bk_engine.active_bets[bid]
552
- if b["status"] != "OPEN":
553
- return await ctx.send("❌ Error: This market is closed.")
554
-
555
  u = bk_engine.get_user(ctx.author.id)
556
- if u["points"] < amt or amt <= 0:
557
- return await ctx.send("❌ Error: Insufficient asset points.")
558
-
559
- side = side.upper()
560
- if side not in ["UP", "DOWN"]:
561
- return await ctx.send("❌ Error: Choose UP or DOWN.")
562
-
563
- # 锁定积分
564
- u["points"] -= amt
565
- uid = str(ctx.author.id)
566
 
567
- pool_key = "u_up" if side == "UP" else "u_down"
 
568
  b[side.lower()] += amt
569
- b[pool_key][uid] = b[pool_key].get(uid, 0) + amt
570
-
571
  bk_engine.save_db()
572
- await ctx.send(f"✅ Position Locked: {ctx.author.name} added {amt} to {side} pool.")
573
 
574
  @bot.command()
575
  @commands.has_permissions(administrator=True)
576
  async def settle_bet(ctx, bid: str, winner: str):
577
- """
578
- 结算盘口并自动执行积分再分配
579
- """
580
  if bid not in bk_engine.active_bets: return
581
- b = bk_engine.active_bets[bid]
582
- winner = winner.upper()
583
-
584
  win_pool = b["u_up"] if winner == "UP" else b["u_down"]
585
- total_win = b["up"] if winner == "UP" else b["down"]
586
- total_lose = b["down"] if winner == "UP" else b["up"]
587
-
588
- if total_win > 0:
589
  for uid, amt in win_pool.items():
590
- # 分配逻辑: 原始积分 + (个人占比 * 输家总积分)
591
- profit = (amt / total_win) * total_lose
592
- user = bk_engine.get_user(int(uid))
593
- user["points"] += round(amt + profit)
594
 
595
  b["status"] = "SETTLED"
596
  bk_engine.save_db()
597
- await ctx.send(f"🏁 Bet `{bid}` Settled! Winner: **{winner}**. Profits Distributed.")
598
 
599
  # ==============================================================================
600
- # 9. 模块五:PVP 竞技场 (Arena PvP Engine)
601
  # ==============================================================================
602
  @bot.command()
603
  async def challenge(ctx, member: discord.Member):
604
- """
605
- 发起基于量化能力的 1对1 竞技
606
- """
607
- if member.id == ctx.author.id:
608
- return await ctx.send("❌ Logical Error: 不能向自己发起决斗。")
609
-
610
- did = f"DUEL-{random.randint(100, 999)}"
611
- bk_engine.pending_challenges[member.id] = {
612
- "challenger": ctx.author.id,
613
- "did": did,
614
- "expiry": datetime.datetime.now() + datetime.timedelta(hours=1)
615
- }
616
-
617
- await ctx.send(f"⚔️ **{ctx.author.name}** 发起决斗请求!目标:{member.mention}\nID: `{did}` | `!accept` 接受。")
618
 
619
  @bot.command()
620
  async def accept(ctx):
621
- """
622
- 接受竞技场挑战
623
- """
624
- if ctx.author.id not in bk_engine.pending_challenges:
625
- return await ctx.send("❌ Error: No pending challenges.")
626
-
627
  req = bk_engine.pending_challenges.pop(ctx.author.id)
628
- did = req["did"]
629
-
630
- bk_engine.active_duels[did] = {
631
- "p1": req["challenger"], "p2": ctx.author.id,
632
- "s1": None, "s2": None,
633
- "start": str(datetime.datetime.now())
634
- }
635
-
636
- await ctx.send(f"🔥 **ARENA LOCKED!** Duel ID: `{did}`\n双方请通过 `!duel_submit {did}` 上传审计截图。")
637
 
638
  @bot.command()
639
  async def duel_submit(ctx, did: str):
640
- """
641
- 在对决中提交截图,执行最终胜负判定
642
- """
643
- if did not in bk_engine.active_duels:
644
- return await ctx.send("❌ Error: Duel not active.")
645
-
646
- if not ctx.message.attachments:
647
- return await ctx.send("❌ Refused: 请上传结算截图。")
648
-
649
  duel = bk_engine.active_duels[did]
650
  if ctx.author.id not in [duel["p1"], duel["p2"]]: return
651
-
652
- m = await ctx.send(f"🔍 **[Arena] Auditing Submission...**")
653
- prompt = "Arena Duel Audit: Identify Audit Score (0-100). FORMAT: SCORE: [val]"
654
- res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
655
  score = safe_extract_float(res, "SCORE")
656
-
657
  if ctx.author.id == duel["p1"]: duel["s1"] = score
658
  else: duel["s2"] = score
659
-
660
- # 逻辑���歧点:如果双方都交了,执行结算
661
  if duel["s1"] is not None and duel["s2"] is not None:
662
- wid, lid = (duel["p1"], duel["p2"]) if duel["s1"] > duel["s2"] else (duel["p2"], duel["p1"])
663
- new_w, new_l, strk = bk_engine.update_elo(wid, lid)
664
-
665
- emb = discord.Embed(title="🏆 Arena Final Verdict", color=0xf1c40f)
666
- emb.add_field(name="🥇 Winner", value=f"<@{wid}>\nScore: {max(duel['s1'], duel['s2']):.1f}\nNew ELO: {new_w}", inline=True)
667
- emb.add_field(name="🥈 Loser", value=f"<@{lid}>\nScore: {min(duel['s1'], duel['s2']):.1f}\nNew ELO: {new_l}", inline=True)
668
- emb.set_footer(text=f"Streak: {strk} | Duel ID: {did}")
669
 
 
 
 
670
  await ctx.send(embed=emb)
671
  del bk_engine.active_duels[did]
672
  else:
673
- await ctx.send(f"✅ Submission Received (Score: {score:.1f}). Waiting for Opponent...")
674
-
675
  await m.delete()
676
 
677
  # ==============================================================================
678
- # 10. 模块六:病毒式海报渲染 (Social Poster Engine)
679
  # ==============================================================================
680
  @bot.command()
681
  async def viral_audit(ctx):
682
  """
683
- 提取审计数据并渲染具备社交传播属性的海报图片
684
  包含完整的 PIL 绘图逻辑,无省略。
685
  """
686
  if not ctx.message.attachments:
@@ -729,52 +547,39 @@ async def viral_audit(ctx):
729
  await m.delete()
730
 
731
  # ==============================================================================
732
- # 11. 系统对账与自动化任务 (System Reliability)
733
  # ==============================================================================
734
  @tasks.loop(hours=1)
735
  async def auto_backup():
736
- """本地资产数据库每小时原子化备份"""
737
  bk_engine.save_db()
738
- logger.info("💾 [Checkpoint] Data persistence executed.")
739
 
740
  @bot.event
741
  async def on_ready():
742
- # 激活心跳对账
743
  if not auto_backup.is_running(): auto_backup.start()
744
-
745
- # 修改机器人活跃状态
746
- await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="HF-Node Ultimate"))
747
 
748
- logger.info(f"--------------------------------------------------")
749
- logger.info(f"✅ [V62.9-HF-Titan] BK-GTA Architecture Online")
750
- logger.info(f"🚀 Session: {SESSION_ID} | Logic Integrity: Full")
751
- logger.info(f"🛡️ Sentinel Port: 7860 (Active)")
752
- logger.info(f"--------------------------------------------------")
753
-
754
- @bot.event
755
- async def on_command_error(ctx, error):
756
- """全局错误拦截器,严禁静默失效"""
757
- if isinstance(error, commands.CommandNotFound): return
758
- if isinstance(error, commands.MissingPermissions):
759
- return await ctx.send("❌ 权限审计未通过:仅限合伙人执行。")
760
-
761
- # 堆栈追踪打印至 Console
762
- exc = "".join(traceback.format_exception(type(error), error, error.__traceback__))
763
- logger.error(f"🔥 [System Crash] {exc}")
764
- await ctx.send(f"⚠️ [Logic Breach] 内部逻辑断裂: `{error}`")
765
-
766
- # ==============================================================================
767
- # 12. 物理点火启动链 (Physical Start Engine)
768
- # ==============================================================================
769
- if __name__ == "__main__":
770
- # 1. 物理启动 Web 哨兵接口 (Port 7860)
771
- keep_alive()
772
-
773
- # 2. 逻辑启动 Discord Bot 核心
774
- if not DISCORD_TOKEN:
775
- logger.critical("❌ CRITICAL: DISCORD_TOKEN is missing in environment.")
776
- else:
777
  try:
778
  bot.run(DISCORD_TOKEN)
779
  except Exception as e:
780
- logger.critical(f"🔥 FINAL ENGINE COLLAPSE: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  import traceback
16
  import io
17
  import time
18
+ import threading
19
  from PIL import Image, ImageDraw, ImageFont
20
+ import gradio as gr # 替换 Flask 为 Gradio
 
21
 
22
  # ==============================================================================
23
+ # 0. 机构级审计日志 (Institutional Logging)
24
  # ==============================================================================
25
  logging.basicConfig(
26
  level=logging.INFO,
27
  format='%(asctime)s [%(levelname)s] %(message)s',
28
  handlers=[logging.StreamHandler(sys.stdout)]
29
  )
30
+ logger = logging.getLogger("BK_GTA_ARCHITECT_GRADIO")
31
 
32
  # ==============================================================================
33
+ # 1. 物理层适配:Gradio 哨兵 (Sentinel Probe)
34
  # ==============================================================================
35
+ def get_status():
 
 
 
36
  """
37
+ 提供给 UptimeRobot 的监控探针内容
38
+ Gradio 会自动将其渲染为一个 API 端点,确保 HF 容器不休眠
 
39
  """
40
  now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
41
  latency = "N/A"
42
  try:
43
+ if bot and bot.is_ready():
 
44
  latency = f"{round(bot.latency * 1000, 2)}ms"
45
+ except:
46
+ pass
47
 
48
  return {
49
+ "status": "BK-GTA-GRADIO-TITAN-ACTIVE",
50
+ "version": "V62.9-Full-Stack",
 
51
  "bot_latency": latency,
52
  "server_time": now,
53
+ "keys_loaded": len(KEY_POOL),
54
+ "memory_usage": f"{sys.getsizeof(bk_engine.data) / 1024:.2f} KB"
55
  }
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  # ==============================================================================
58
  # 2. 密钥池与环境配置 (Key Rotation Engine)
59
  # ==============================================================================
 
61
  SESSION_ID = random.randint(10000, 99999)
62
 
63
  # [CORE] 8 密钥轮询加载器
 
64
  raw_keys = os.environ.get('GEMINI_KEYS_POOL', '')
65
  KEY_POOL = []
66
 
 
87
  intents.members = True
88
  bot = commands.Bot(command_prefix='!', intents=intents)
89
 
90
+ # 并发审计锁 (防止内存溢出)
91
  audit_lock = asyncio.Lock()
92
 
93
+ # 数据库路径适配 (Gradio 模式也支持 /data 持久化)
94
  if os.path.exists('/data'):
95
  DB_PATH_CONFIG = '/data/bk_gta_v62_unified.json'
96
  logger.info(f"📂 [Storage] Using Persistent Storage: {DB_PATH_CONFIG}")
97
  else:
98
  DB_PATH_CONFIG = 'bk_gta_v62_unified.json'
99
+ logger.warning(f"⚠️ [Storage] Using Ephemeral Storage (Data lost on restart).")
100
 
101
  # ==============================================================================
102
+ # 3. 状态引擎 (BKEngine Core)
103
  # ==============================================================================
104
  class BKEngine:
 
 
 
105
  def __init__(self):
106
  self.db_file = DB_PATH_CONFIG
107
  self.data = self._load_db()
108
  self.pending_challenges = {}
109
  self.active_duels = {}
110
  self.active_bets = {}
 
111
 
112
  def _load_db(self):
113
  if os.path.exists(self.db_file):
 
123
 
124
  def _init_schema(self):
125
  return {
126
+ "version": "62.9-Gradio",
127
  "users": {},
128
  "factions": {
129
  "BULLS": {"score": 0, "emoji": "🐂", "total_wins": 0},
 
135
  }
136
 
137
  def save_db(self):
 
138
  try:
139
  # 原子写入防止文件损坏
140
  temp = self.db_file + '.tmp'
 
157
  "win_streak": 0,
158
  "mdd_record": 0.0,
159
  "sharpe_record": 0.0,
 
160
  "last_active": str(datetime.datetime.now())
161
  }
162
  return self.data["users"][uid]
 
165
  u = self.get_user(user_id)
166
  u["audits_count"] += 1
167
  u["total_score"] += score
168
+ if score > u.get("highest_score", 0): u["highest_score"] = score
 
169
  if mdd is not None: u["mdd_record"] = mdd
170
  if sharpe is not None: u["sharpe_record"] = sharpe
 
 
171
  u["points"] += 50
172
  u["last_active"] = str(datetime.datetime.now())
 
173
  self.save_db()
174
 
175
  def update_elo(self, winner_id, loser_id):
176
+ w, l = self.get_user(winner_id), self.get_user(loser_id)
 
 
 
 
 
 
177
  K = 32
178
  Rw, Rl = w["elo"], l["elo"]
179
  Ew = 1 / (1 + 10 ** ((Rl - Rw) / 400))
 
180
  w["elo"] = round(Rw + K * (1 - Ew))
181
  l["elo"] = round(Rl + K * (0 - Ew))
 
182
  w["win_streak"] += 1
183
  l["win_streak"] = 0
184
+ if w["faction"]:
 
 
185
  self.data["factions"][w["faction"]]["score"] += 15
186
  self.data["factions"][w["faction"]]["total_wins"] += 1
 
187
  self.save_db()
188
  return w["elo"], l["elo"], w["win_streak"]
189
 
190
  bk_engine = BKEngine()
191
 
192
  # ==============================================================================
193
+ # 4. 辅助内核 (Helper Kernels)
194
  # ==============================================================================
195
  def safe_extract_float(text, key):
 
196
  try:
197
  patterns = [
198
+ rf"{key}:\s*([-+]?\d*\.?\d+)",
199
  rf"{key}\s*=\s*([-+]?\d*\.?\d+)",
200
  rf"【{key}】\s*([-+]?\d*\.?\d+)"
201
  ]
202
+ for p in patterns:
203
+ match = re.search(p, text, re.IGNORECASE)
204
  if match: return float(match.group(1))
205
  return 0.0
206
+ except: return 0.0
207
 
208
  def safe_extract_text(text, key_start, key_end=None):
 
209
  try:
210
+ p = rf"{key_start}:(.*?){key_end}:" if key_end else rf"{key_start}:(.*)"
211
+ match = re.search(p, text, re.DOTALL | re.IGNORECASE)
 
 
 
212
  if match:
213
+ return match.group(1).strip().replace('*', '').replace('`', '').strip()
 
 
214
  return "N/A"
215
+ except: return "N/A"
216
 
217
  async def call_gemini_multiplex(prompt, attachment=None):
218
  """
219
  [Core Logic] 8 密钥自动轮询与故障转移
 
220
  """
221
+ if not KEY_POOL: return "ERROR: NO_KEYS_CONFIGURED"
 
 
222
 
223
  image_data = None
224
  if attachment:
225
+ try: image_data = await attachment.read()
226
+ except: return "ERROR: IMG_READ_FAIL"
 
 
 
227
 
228
  loop = asyncio.get_running_loop()
 
 
229
  shuffled_keys = list(KEY_POOL)
230
  random.shuffle(shuffled_keys)
231
 
232
  for i, api_key in enumerate(shuffled_keys):
233
  try:
 
234
  client = genai.Client(api_key=api_key)
235
+ contents = [prompt]
236
  if image_data:
237
+ contents.append(types.Part.from_bytes(data=image_data, mime_type='image/png'))
238
 
 
239
  response = await loop.run_in_executor(
240
  None,
241
+ functools.partial(client.models.generate_content, model="gemini-1.5-flash", contents=contents)
242
  )
243
 
244
+ if response and response.text: return response.text
 
245
 
246
  except Exception as e:
247
  err_msg = str(e)
248
  if "429" in err_msg:
249
+ logger.warning(f"⚠️ Key {i+1} Rate Limited. Switching...")
250
+ continue
251
+ logger.error(f"🤖 Key {i+1} Error: {err_msg}")
252
+ continue
 
 
 
 
253
 
254
  return "ERROR: ALL_KEYS_EXHAUSTED"
255
 
256
  # ==============================================================================
257
+ # 5. 模块一:全网天梯 (Leaderboard)
258
  # ==============================================================================
259
  @bot.command()
260
  async def profile(ctx, member: discord.Member = None):
 
 
 
261
  target = member or ctx.author
262
  u = bk_engine.get_user(target.id)
 
 
263
  elo = u['elo']
264
+
265
+ # 完整段位逻辑
266
  if elo < 1300: rank, color = "Iron (学徒) 🗑️", 0x717d7e
267
  elif elo < 1500: rank, color = "Bronze (资深) 🥉", 0xcd7f32
268
  elif elo < 1800: rank, color = "Silver (精英) 🥈", 0xbdc3c7
 
273
  emb = discord.Embed(title=f"📊 Quantitative Profile: {target.display_name}", color=color)
274
  emb.add_field(name="Rating (ELO)", value=f"**{elo}**\nRank: `{rank}`", inline=True)
275
  emb.add_field(name="Assets (Points)", value=f"💰 **{u['points']}**", inline=True)
276
+
277
+ f_info = u['faction'] or "Free Agent"
278
+ f_emoji = bk_engine.data["factions"].get(f_info, {}).get("emoji", "")
279
+ emb.add_field(name="Faction", value=f"{f_emoji} {f_info}", inline=True)
280
 
281
+ stats = (
 
 
 
 
 
282
  f"Total Audits: {u['audits_count']}\n"
283
+ f"Peak Score: {u['highest_score']:.1f}\n"
284
+ f"Max MDD: {u.get('mdd_record',0):.2f}%"
 
285
  )
286
+ emb.add_field(name="Performance Metrics", value=f"```yaml\n{stats}\n```", inline=False)
 
 
 
287
 
288
+ if target.avatar: emb.set_thumbnail(url=target.avatar.url)
289
+ emb.set_footer(text=f"Session: {SESSION_ID} | Node: HuggingFace")
290
  await ctx.send(embed=emb)
291
 
292
  @bot.command()
293
  async def leaderboard(ctx):
294
+ users = [{"id": k, "elo": v["elo"], "score": v["highest_score"]} for k,v in bk_engine.data["users"].items()]
295
+ top = sorted(users, key=lambda x: x["elo"], reverse=True)[:10]
296
+
 
 
 
 
 
 
 
297
  desc = ""
298
+ for i, u in enumerate(top):
299
+ icon = ["🥇","🥈","🥉"][i] if i < 3 else f"{i+1}."
300
+ desc += f"{icon} <@{u['id']}> : **{u['elo']}** (Peak: {u['score']:.1f})\n"
301
+
302
+ emb = discord.Embed(title="🏆 Global Ranking (Institutional)", description=desc or "No data available.", color=0xf1c40f)
303
+
304
+ # 阵营战况
305
+ f_txt = ""
306
+ for k, v in bk_engine.data["factions"].items():
307
+ f_txt += f"{v['emoji']} {k}: {v['score']} pts | "
308
+ if f_txt: emb.add_field(name="Faction War Status", value=f_txt, inline=False)
309
 
 
 
 
 
 
 
 
310
  await ctx.send(embed=emb)
311
 
312
  # ==============================================================================
313
+ # 6. 模块二:宏观情报 (Macro Intelligence)
314
  # ==============================================================================
315
  @bot.command()
316
  async def news(ctx):
317
+ if not ctx.message.attachments: return await ctx.send("❌ Audit Refused: Please upload an image.")
318
+ m = await ctx.send("📡 **[HF-Node] Analyzing Macro Pulse...**")
319
+
 
 
 
 
 
320
  prompt = (
321
+ "Analyze chart/news. Output: SCORE: (-10 to 10), SUMMARY: (text), TAKE: (institutional view). "
322
+ "Format: SCORE: [v] SUMMARY: [v] TAKE: [v]"
 
 
 
 
 
 
323
  )
 
324
  res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
325
 
 
 
 
 
326
  score = safe_extract_float(res, "SCORE")
327
  summary = safe_extract_text(res, "SUMMARY", "TAKE")
328
  take = safe_extract_text(res, "TAKE")
329
+
330
+ emb = discord.Embed(title="📰 Macro Intelligence", color=0x2ecc71 if score > 0 else 0xe74c3c)
331
+ emb.add_field(name="Sentiment Score", value=f"**{score:+.1f}**")
332
+ emb.add_field(name="Executive Summary", value=summary, inline=False)
333
+ emb.add_field(name="Institutional Take", value=f"```{take}```", inline=False)
334
+
 
335
  await m.delete()
336
  await ctx.send(embed=emb)
337
 
 
341
  @bot.command()
342
  async def evaluate(ctx):
343
  """
344
+ 带有并发锁的审计功能,防止 HF 容器 OOM
 
345
  """
346
+ if not ctx.message.attachments: return await ctx.send("❌ No image detected.")
347
+
 
348
  # [Lock Check] 检查是否忙碌
349
  if audit_lock.locked():
350
+ return await ctx.send("⚠️ **[System Busy]** Another audit is processing. Please wait 10s.")
351
 
352
  async with audit_lock:
353
+ m = await ctx.send("🛡️ **[Audit Engine] Processing... (Locked)**")
 
 
 
354
  prompt = (
355
+ "Quant Audit. Extract RET, MDD, PF, SHARPE, TRADES. S_BASE(0-100). "
356
+ "VIOLATION(TRUE if MDD>35). Highlights(H_ZH). "
357
+ "Format: RET:[v] MDD:[v] PF:[v] SHARPE:[v] TRADES:[v] S_BASE:[v] VIOLATION:[v] H_ZH:[v]"
 
 
 
 
 
358
  )
359
+
360
  res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
361
+
362
  if "ERROR" in res:
363
  await m.delete()
364
+ return await ctx.send(f"🔴 Audit Failed: {res}")
365
 
 
366
  ret = safe_extract_float(res, "RET")
367
  mdd = safe_extract_float(res, "MDD")
368
  pf = safe_extract_float(res, "PF")
 
371
  s_base = safe_extract_float(res, "S_BASE")
372
  violation = "TRUE" in res.upper()
373
 
 
 
374
  if violation or mdd > 35.0:
375
  s_final = 0.0
376
+ status = "REJECTED (High Risk)"
377
  color = 0xe74c3c
378
  else:
379
+ # 完整评分公式
380
  trades_adj = math.log10(max(trades, 1)) / 1.5
381
+ s_final = s_base * min(max(trades_adj, 0.5), 1.2) * (sharpe/2.0 if sharpe>0 else 0.5)
382
  s_final = min(max(s_final, 0.0), 100.0)
383
+ status = "CLEARED"
384
  color = 0x2ecc71
385
 
 
386
  bk_engine.update_audit_stats(ctx.author.id, s_final, mdd, sharpe)
 
 
 
 
387
 
388
+ emb = discord.Embed(title="🛡️ Audit Report", color=color)
389
+ emb.add_field(name="Verdict", value=status, inline=False)
390
+ emb.add_field(name="Metrics", value=f"```Return: {ret}%\nMDD: {mdd}%\nSharpe: {sharpe}\nPF: {pf}```", inline=False)
391
+ emb.add_field(name="Score", value=f"**{s_final:.1f}**")
392
+ emb.add_field(name="Highlights", value=safe_extract_text(res, "H_ZH"), inline=False)
 
 
 
393
 
 
394
  await m.delete()
395
  await ctx.send(embed=emb)
396
 
397
  # ==============================================================================
398
+ # 8. 模块四:博弈盘口 (Betting System)
399
  # ==============================================================================
400
  @bot.command()
401
  @commands.has_permissions(administrator=True)
402
+ async def open_bet(ctx, *, topic):
 
 
 
403
  bid = str(random.randint(100, 999))
404
  bk_engine.active_bets[bid] = {
405
+ "topic": topic,
406
+ "up": 0, "down": 0,
407
+ "u_up": {}, "u_down": {},
408
  "status": "OPEN",
409
  "start_time": str(datetime.datetime.now())
410
  }
411
+ await ctx.send(f"🎰 **Bet Market Open [{bid}]**\nTopic: {topic}\nType `!bet {bid} UP 100` to join.")
412
 
413
  @bot.command()
414
  async def bet(ctx, bid: str, side: str, amt: int):
415
+ if bid not in bk_engine.active_bets or bk_engine.active_bets[bid]["status"] != "OPEN": return
 
 
 
 
 
 
 
 
 
416
  u = bk_engine.get_user(ctx.author.id)
417
+ if u["points"] < amt: return await ctx.send("❌ Insufficient funds.")
418
+
419
+ u["points"] -= amt; side = side.upper()
420
+ if side not in ["UP", "DOWN"]: return await ctx.send("❌ Choose UP or DOWN.")
 
 
 
 
 
 
421
 
422
+ pool = "u_up" if side == "UP" else "u_down"
423
+ b = bk_engine.active_bets[bid]
424
  b[side.lower()] += amt
425
+ b[pool][str(ctx.author.id)] = b[pool].get(str(ctx.author.id), 0) + amt
 
426
  bk_engine.save_db()
427
+ await ctx.send(f"✅ Bet {amt} on {side} confirmed.")
428
 
429
  @bot.command()
430
  @commands.has_permissions(administrator=True)
431
  async def settle_bet(ctx, bid: str, winner: str):
 
 
 
432
  if bid not in bk_engine.active_bets: return
433
+ b = bk_engine.active_bets[bid]; winner = winner.upper()
434
+
 
435
  win_pool = b["u_up"] if winner == "UP" else b["u_down"]
436
+ total_w = b["up"] if winner == "UP" else b["down"]
437
+ total_l = b["down"] if winner == "UP" else b["up"]
438
+
439
+ if total_w > 0:
440
  for uid, amt in win_pool.items():
441
+ profit = (amt/total_w) * total_l
442
+ bk_engine.get_user(int(uid))["points"] += int(amt + profit)
 
 
443
 
444
  b["status"] = "SETTLED"
445
  bk_engine.save_db()
446
+ await ctx.send(f"🏁 Bet {bid} Settled. Winner: {winner}. Payouts Distributed.")
447
 
448
  # ==============================================================================
449
+ # 9. 模块五:PVP 竞技场 (Arena)
450
  # ==============================================================================
451
  @bot.command()
452
  async def challenge(ctx, member: discord.Member):
453
+ if member.id == ctx.author.id: return
454
+ did = f"D-{random.randint(1000,9999)}"
455
+ bk_engine.pending_challenges[member.id] = {"c": ctx.author.id, "id": did}
456
+ await ctx.send(f"⚔️ {ctx.author.name} challenges {member.name}!\nID: `{did}`. Type `!accept` to start.")
 
 
 
 
 
 
 
 
 
 
457
 
458
  @bot.command()
459
  async def accept(ctx):
460
+ if ctx.author.id not in bk_engine.pending_challenges: return
 
 
 
 
 
461
  req = bk_engine.pending_challenges.pop(ctx.author.id)
462
+ bk_engine.active_duels[req["id"]] = {"p1": req["c"], "p2": ctx.author.id, "s1": None, "s2": None}
463
+ await ctx.send(f"🔥 Duel `{req['id']}` Active! Submit screenshots via `!duel_submit {req['id']}`")
 
 
 
 
 
 
 
464
 
465
  @bot.command()
466
  async def duel_submit(ctx, did: str):
467
+ if did not in bk_engine.active_duels: return
468
+ if not ctx.message.attachments: return
469
+
 
 
 
 
 
 
470
  duel = bk_engine.active_duels[did]
471
  if ctx.author.id not in [duel["p1"], duel["p2"]]: return
472
+
473
+ m = await ctx.send("🔍 Auditing duel submission...")
474
+ res = await call_gemini_multiplex("Extract SCORE:[0-100]. Format: SCORE:[v]", ctx.message.attachments[0])
 
475
  score = safe_extract_float(res, "SCORE")
476
+
477
  if ctx.author.id == duel["p1"]: duel["s1"] = score
478
  else: duel["s2"] = score
479
+
 
480
  if duel["s1"] is not None and duel["s2"] is not None:
481
+ p1, p2 = duel["p1"], duel["p2"]
482
+ s1, s2 = duel["s1"], duel["s2"]
483
+ wid, lid = (p1, p2) if s1 > s2 else (p2, p1)
484
+ w_elo, l_elo, _ = bk_engine.update_elo(wid, lid)
 
 
 
485
 
486
+ emb = discord.Embed(title="🏆 Duel Result", color=0xf1c40f)
487
+ emb.add_field(name="Winner", value=f"<@{wid}>\nScore: {max(s1,s2)}\nELO: {w_elo}")
488
+ emb.add_field(name="Loser", value=f"<@{lid}>\nScore: {min(s1,s2)}\nELO: {l_elo}")
489
  await ctx.send(embed=emb)
490
  del bk_engine.active_duels[did]
491
  else:
492
+ await ctx.send(f"✅ Score {score} recorded. Waiting for opponent.")
 
493
  await m.delete()
494
 
495
  # ==============================================================================
496
+ # 10. 模块六:海报渲染 (Full Viral Poster)
497
  # ==============================================================================
498
  @bot.command()
499
  async def viral_audit(ctx):
500
  """
501
+ [完整版] 提取审计数据并渲染具备社交传播属性的海报图片
502
  包含完整的 PIL 绘图逻辑,无省略。
503
  """
504
  if not ctx.message.attachments:
 
547
  await m.delete()
548
 
549
  # ==============================================================================
550
+ # 11. 启动引擎 (Gradio + Discord Thread)
551
  # ==============================================================================
552
  @tasks.loop(hours=1)
553
  async def auto_backup():
 
554
  bk_engine.save_db()
 
555
 
556
  @bot.event
557
  async def on_ready():
 
558
  if not auto_backup.is_running(): auto_backup.start()
559
+ await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="HF-Native Node"))
560
+ logger.info(f"✅ [V62.9-Gradio] Online. Session: {SESSION_ID}")
 
561
 
562
+ def run_discord_bot():
563
+ if DISCORD_TOKEN:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  try:
565
  bot.run(DISCORD_TOKEN)
566
  except Exception as e:
567
+ logger.critical(f"Bot Crash: {e}")
568
+ else:
569
+ logger.critical("No Token")
570
+
571
+ if __name__ == "__main__":
572
+ # 1. 在后台线程启动 Discord 机器人 (非阻塞)
573
+ threading.Thread(target=run_discord_bot).start()
574
+
575
+ # 2. 在主线程启动 Gradio UI (这是 HF 要求的保活入口)
576
+ # 这会生成一个网页,显示 JSON 状态,UptimeRobot 访问这个网页就能保活
577
+ iface = gr.Interface(
578
+ fn=get_status,
579
+ inputs=None,
580
+ outputs="json",
581
+ title="BK-GTA Sentinel",
582
+ description="Core logic is running in background thread.",
583
+ every=5 # 自动刷新
584
+ )
585
+ iface.launch(server_name="0.0.0.0", server_port=7860)