max1949 commited on
Commit
113d688
·
verified ·
1 Parent(s): 62f32c3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +382 -419
app.py CHANGED
@@ -16,6 +16,7 @@ import traceback
16
  import io
17
  import time
18
  import threading
 
19
  from PIL import Image, ImageDraw, ImageFont
20
  import gradio as gr
21
 
@@ -29,6 +30,35 @@ logging.basicConfig(
29
  )
30
  logger = logging.getLogger("BK_GTA_ARCHITECT_TITAN")
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  # ==============================================================================
33
  # 1. 物理层:Gradio 哨兵与探针 (Gradio Sentinel)
34
  # ==============================================================================
@@ -46,7 +76,7 @@ def get_status():
46
  return {
47
  "status": "BK-GTA-TITAN-ACTIVE",
48
  "logic_integrity": "100%",
49
- "engine_version": "V62.9-Gradio-Final",
50
  "discord_bot": bot_status,
51
  "latency": latency,
52
  "server_time": now,
@@ -72,7 +102,12 @@ if not KEY_POOL:
72
  intents = discord.Intents.default()
73
  intents.message_content = True
74
  intents.members = True
75
- bot = commands.Bot(command_prefix='!', intents=intents)
 
 
 
 
 
76
 
77
  audit_lock = asyncio.Lock()
78
  DB_PATH = '/data/bk_gta_v62_unified.json' if os.path.exists('/data') else 'bk_gta_v62_unified.json'
@@ -102,7 +137,7 @@ class BKEngine:
102
 
103
  def _init_schema(self):
104
  return {
105
- "version": "62.9-Titan",
106
  "users": {},
107
  "factions": {
108
  "BULLS": {"score": 0, "emoji": "🐂", "wins": 0},
@@ -224,407 +259,387 @@ async def call_gemini(prompt, attachment=None):
224
  return "ERROR: ALL_KEYS_EXHAUSTED"
225
 
226
  # ==============================================================================
227
- # 5. 模块一:全网天梯 (Rank & Stats)
228
  # ==============================================================================
229
- @bot.command()
230
- async def profile(ctx, member: discord.Member = None):
231
- t = member or ctx.author
232
- u = bk_engine.get_user(t.id)
233
- elo = u['elo']
234
-
235
- if elo < 1300: rank, color = "Iron 学徒 🗑️", 0x717d7e
236
- elif elo < 1500: rank, color = "Bronze 资深 🥉", 0xcd7f32
237
- elif elo < 1800: rank, color = "Silver 精英 🥈", 0xbdc3c7
238
- elif elo < 2100: rank, color = "Gold 专家 🥇", 0xf1c40f
239
- elif elo < 2500: rank, color = "Platinum 大师 💎", 0x3498db
240
- else: rank, color = "Grandmaster 传奇 👑", 0x9b59b6
241
-
242
- emb = discord.Embed(title=f"📊 Quant Profile: {t.display_name}", color=color)
243
- emb.add_field(name="Rating (ELO)", value=f"**{elo}** ({rank})", inline=True)
244
- emb.add_field(name="Assets", value=f"💰 **{u['points']}**", inline=True)
245
-
246
- f_info = u['faction'] or "Free Agent"
247
- f_emoji = bk_engine.data["factions"].get(f_info, {}).get("emoji", "🌐")
248
- emb.add_field(name="Faction", value=f"{f_emoji} {f_info}", inline=True)
249
-
250
- stats = (
251
- f"Total Audits: {u['audits_count']}\n"
252
- f"Peak Score: {u['highest_score']:.1f}\n"
253
- f"Max MDD: {u.get('mdd_record',0):.2f}%"
254
- )
255
- emb.add_field(name="Institutional Metrics", value=f"```yaml\n{stats}\n```", inline=False)
256
-
257
- if target_avatar := t.avatar:
258
- emb.set_thumbnail(url=target_avatar.url)
259
- emb.set_footer(text=f"Node: HuggingFace Titan | Session: {SESSION_ID}")
260
- await ctx.send(embed=emb)
261
-
262
- @bot.command()
263
- async def leaderboard(ctx):
264
- all_users = []
265
- for uid, d in bk_engine.data["users"].items():
266
- all_users.append({"id": uid, "elo": d["elo"], "score": d["highest_score"]})
267
-
268
- top = sorted(all_users, key=lambda x: x["elo"], reverse=True)[:10]
269
- desc = ""
270
- for idx, u in enumerate(top):
271
- icon = ["🥇","🥈","🥉"][idx] if idx < 3 else f"{idx+1}."
272
- desc += f"{icon} <@{u['id']}> : **{u['elo']}** (Peak: {u['score']:.1f})\n"
273
-
274
- emb = discord.Embed(title="🏆 BK-GTA Institutional Leaderboard", description=desc or "No data.", color=0xf1c40f)
275
-
276
- f_info = ""
277
- for k, v in bk_engine.data["factions"].items():
278
- f_info += f"{v['emoji']} {k}: {v['score']} pts "
279
- if f_info:
280
- emb.add_field(name="⚔️ Faction War", value=f_info, inline=False)
281
-
282
- await ctx.send(embed=emb)
283
 
284
- # ==============================================================================
285
- # 6. 模块二:宏观情报 (Macro Analysis)
286
- # ==============================================================================
287
- @bot.command()
288
- async def news(ctx):
289
- if not ctx.message.attachments: return await ctx.send("❌ Error: 请上传情报截图。")
290
- m = await ctx.send("📡 **[Sentinel] Analyzing Macro Pulse...**")
291
-
292
- prompt = (
293
- "你是一名顶级对冲基金策略师。请解析截图内容:\n"
294
- "1. 评分 (SCORE): -10 (极大利空) 到 +10 (极大利多)。\n"
295
- "2. 总结 (SUMMARY): 30字内概括核心变量。\n"
296
- "3. 解读 (TAKE): 机构视角的隐含逻辑。\n"
297
- "格式必须为:\nSCORE: [v]\nSUMMARY: [v]\nTAKE: [v]"
298
- )
299
- res = await call_gemini(prompt, ctx.message.attachments[0])
300
-
301
- score = safe_extract_float(res, "SCORE")
302
- summary = safe_extract_text(res, "SUMMARY", "TAKE")
303
- take = safe_extract_text(res, "TAKE")
304
-
305
- color = 0x2ecc71 if score > 0 else 0xe74c3c if score < 0 else 0xbdc3c7
306
- emb = discord.Embed(title="📰 Macro Intelligence Analysis", color=color)
307
- emb.add_field(name="Sentiment Score", value=f"**{score:+.1f} / 10**", inline=True)
308
- emb.add_field(name="Core Catalyst", value=summary, inline=False)
309
- emb.add_field(name="Institutional View", value=f"```\n{take}\n```", inline=False)
310
-
311
- await m.delete()
312
- await ctx.send(embed=emb)
313
 
314
- # ==============================================================================
315
- # 7. 模块三:量化审计 (Institutional Audit)
316
- # ==============================================================================
317
- @bot.command()
318
- async def evaluate(ctx):
319
- if not ctx.message.attachments: return await ctx.send("❌ Error: 请上传回测或结算截图。")
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
- if audit_lock.locked():
322
- return await ctx.send("⚠️ **[System Busy]** 正在处理另一项审计,请稍后。")
 
 
323
 
324
- async with audit_lock:
325
- m = await ctx.send("🛡️ **[Audit Engine] 启动深度扫描... (锁闭模式)**")
326
  prompt = (
327
- "你是一名顶级量化风险官。请提取并审计以下指标:\n"
328
- "RET: 收益率, MDD: 最大回撤, PF: 盈利因子, SHARPE: 夏普比率, TRADES: 总笔数, S_BASE: 基础评分(0-100)。\n"
329
- " MDD > 35% 或交易笔数 < 10,则 VIOLATION 为 TRUE。\n"
330
- "H_ZH: 中文核心优势或致命风险概括。\n"
331
- "格式:\nRET: [v]\nMDD: [v]\nPF: [v]\nSHARPE: [v]\nTRADES: [v]\nS_BASE: [v]\nVIOLATION: [TRUE/FALSE]\nH_ZH: [v]"
332
  )
333
-
334
  res = await call_gemini(prompt, ctx.message.attachments[0])
335
 
336
- if "ERROR" in res:
337
- await m.delete()
338
- return await ctx.send("🔴 [Audit Crash] 引擎请求失败。")
339
-
340
- ret = safe_extract_float(res, "RET")
341
- mdd = safe_extract_float(res, "MDD")
342
- sharpe = safe_extract_float(res, "SHARPE")
343
- trades = int(safe_extract_float(res, "TRADES"))
344
- s_base = safe_extract_float(res, "S_BASE")
345
- violation = "TRUE" in res.upper()
346
-
347
- if violation or mdd > 35.0:
348
- s_final = 0.0
349
- status = "❌ REJECTED (High Risk)"
350
- color = 0xe74c3c
351
- else:
352
- weight_sample = math.log10(max(trades, 1)) / 1.5
353
- weight_risk = (sharpe / 2.0) if sharpe > 0 else 0.5
354
- s_final = min(max(s_base * weight_sample * weight_risk, 0.0), 100.0)
355
- status = "✅ AUDIT CLEARED"
356
- color = 0x2ecc71
357
-
358
- bk_engine.update_audit(ctx.author.id, s_final, mdd, sharpe)
359
- highlights = safe_extract_text(res, "H_ZH")
360
-
361
- emb = discord.Embed(title="🛡️ Quantitative Audit Report", color=color)
362
- emb.add_field(name="Verdict", value=f"**{status}**", inline=False)
363
- metrics = f"Return: {ret}% | MDD: {mdd}%\nSharpe: {sharpe} | Trades: {trades}"
364
- emb.add_field(name="Key Metrics", value=f"```\n{metrics}\n```", inline=False)
365
- emb.add_field(name="Logic Score", value=f"**{s_final:.2f} / 100**", inline=True)
366
- emb.add_field(name="Highlights", value=highlights, inline=False)
367
 
368
  await m.delete()
369
  await ctx.send(embed=emb)
370
 
371
- # ==============================================================================
372
- # 8. 模块四:博弈盘口 (Institutional Betting)
373
- # ==============================================================================
374
- @bot.command()
375
- @commands.has_permissions(administrator=True)
376
- async def open_bet(ctx, *, topic):
377
- bid = str(random.randint(100, 999))
378
- bk_engine.active_bets[bid] = {
379
- "topic": topic, "up": 0, "down": 0,
380
- "u_up": {}, "u_down": {}, "status": "OPEN",
381
- "time": str(datetime.datetime.now())
382
- }
383
- await ctx.send(f"🎰 **[Bet-ID:{bid}]** 新博弈盘口已开启!\n主题:**{topic}**\n输入 `!bet {bid} UP/DOWN [金额]` 参与。")
384
-
385
- @bot.command()
386
- async def bet(ctx, bid: str, side: str, amt: int):
387
- if bid not in bk_engine.active_bets or bk_engine.active_bets[bid]["status"] != "OPEN":
388
- return await ctx.send("❌ 盘口不存在或已关闭。")
389
-
390
- u = bk_engine.get_user(ctx.author.id)
391
- if u["points"] < amt or amt <= 0:
392
- return await ctx.send("❌ 资产点数不足。")
393
-
394
- side = side.upper()
395
- if side not in ["UP", "DOWN"]: return await ctx.send("❌ 必须选择 UP 或 DOWN。")
396
-
397
- u["points"] -= amt
398
- b = bk_engine.active_bets[bid]
399
- pool = "u_up" if side == "UP" else "u_down"
400
- b[side.lower()] += amt
401
- b[pool][str(ctx.author.id)] = b[pool].get(str(ctx.author.id), 0) + amt
402
-
403
- bk_engine.save_db()
404
- await ctx.send(f"✅ 已确认:{ctx.author.name} 向 {side} 注入 {amt} 点资产。")
405
-
406
- @bot.command()
407
- @commands.has_permissions(administrator=True)
408
- async def settle_bet(ctx, bid: str, winner: str):
409
- if bid not in bk_engine.active_bets: return
410
- b = bk_engine.active_bets[bid]
411
- winner = winner.upper()
412
-
413
- win_pool = b["u_up"] if winner == "UP" else b["u_down"]
414
- total_w = b["up"] if winner == "UP" else b["down"]
415
- total_l = b["down"] if winner == "UP" else b["up"]
416
-
417
- if total_w > 0:
418
- for uid, amt in win_pool.items():
419
- profit = (amt / total_w) * total_l
420
- bk_engine.get_user(int(uid))["points"] += int(amt + profit)
421
-
422
- b["status"] = "SETTLED"
423
- bk_engine.save_db()
424
- await ctx.send(f"🏁 盘口 {bid} 已结算!胜利方:**{winner}**。资产已自动划转。")
425
 
426
- # ==============================================================================
427
- # 9. 模块五:PVP 竞技场 (Duel Arena)
428
- # ==============================================================================
429
- @bot.command()
430
- async def challenge(ctx, member: discord.Member):
431
- if member.id == ctx.author.id: return
432
- did = f"D-{random.randint(1000, 9999)}"
433
- bk_engine.pending_challenges[member.id] = {"challenger": ctx.author.id, "id": did}
434
- await ctx.send(f"⚔️ {ctx.author.name} 向 {member.mention} 发起挑战!\nID: `{did}`. 输入 `!accept` 接受。")
435
-
436
- @bot.command()
437
- async def accept(ctx):
438
- if ctx.author.id not in bk_engine.pending_challenges:
439
- return await ctx.send("❌ 没有待处理的决斗请求。")
440
-
441
- req = bk_engine.pending_challenges.pop(ctx.author.id)
442
- did = req["id"]
443
- bk_engine.active_duels[did] = {
444
- "p1": req["challenger"], "p2": ctx.author.id,
445
- "s1": None, "s2": None, "t": str(datetime.datetime.now())
446
- }
447
- await ctx.send(f"🔥 **竞技场锁定!** 决斗 ID: `{did}`\n请双方通过 `!duel_submit {did}` 提交审计截图。")
 
 
 
 
 
 
 
 
 
 
 
448
 
449
- @bot.command()
450
- async def duel_submit(ctx, did: str):
451
- if did not in bk_engine.active_duels: return
452
- if not ctx.message.attachments: return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
 
454
- duel = bk_engine.active_duels[did]
455
- if ctx.author.id not in [duel["p1"], duel["p2"]]: return
 
 
456
 
457
- m = await ctx.send("🔍 正在审计决斗数据...")
458
- res = await call_gemini("Extract SCORE:[0-100]. Format: SCORE:[v]", ctx.message.attachments[0])
459
- score = safe_extract_float(res, "SCORE")
460
 
461
- if ctx.author.id == duel["p1"]: duel["s1"] = score
462
- else: duel["s2"] = score
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
 
464
- if duel["s1"] is not None and duel["s2"] is not None:
465
- p1, p2 = duel["p1"], duel["p2"]
466
- s1, s2 = duel["s1"], duel["s2"]
467
- wid, lid = (p1, p2) if s1 > s2 else (p2, p1)
468
- w_elo, l_elo, streak = bk_engine.update_elo(wid, lid)
 
 
 
 
469
 
470
- emb = discord.Embed(title="🏆 Duel Arena Verdict", color=0xf1c40f)
471
- emb.add_field(name="Winner", value=f"<@{wid}>\nScore: {max(s1,s2)}\nELO: {w_elo}", inline=True)
472
- emb.add_field(name="Loser", value=f"<@{lid}>\nScore: {min(s1,s2)}\nELO: {l_elo}", inline=True)
473
- emb.set_footer(text=f"Win Streak: {streak} | Duel ID: {did}")
474
- await ctx.send(embed=emb)
475
- del bk_engine.active_duels[did]
476
- else:
477
- await ctx.send(f"✅ 交易员 <@{ctx.author.id}> 分数已锁定 ({score})。等待对手提交。")
478
- await m.delete()
479
 
480
- # ==============================================================================
481
- # 10. 模块六:海报渲染引擎 (Poster Rendering)
482
- # ==============================================================================
483
- @bot.command()
484
- async def viral_audit(ctx):
485
- if not ctx.message.attachments: return
486
- m = await ctx.send("🎨 **[GPU-Node] 正在渲染视觉海报...**")
487
 
488
- p = "Extract RET: [v] and SHARPE: [v]. Format: RET: [v] SHARPE: [v]"
489
- res = await call_gemini(p, ctx.message.attachments[0])
490
- ret, sharpe = safe_extract_float(res, "RET"), safe_extract_float(res, "SHARPE")
491
 
492
- try:
493
- W, H = 600, 800
494
- base = Image.new('RGB', (W, H), (18, 20, 30))
495
- draw = ImageDraw.Draw(base)
496
-
497
- for i in range(0, W, 40): draw.line([(i, 0), (i, H)], fill=(30, 35, 45), width=1)
498
- for j in range(0, H, 40): draw.line([(0, j), (W, j)], fill=(30, 35, 45), width=1)
499
-
500
- draw.rectangle([10, 10, 590, 790], outline=(46, 204, 113), width=2)
501
- draw.rectangle([20, 20, 580, 780], outline=(46, 204, 113), width=5)
502
-
503
- draw.text((40, 60), "BK-GTA QUANTITATIVE SYSTEM", fill=(255, 255, 255))
504
- draw.text((40, 150), f"TRADER: {ctx.author.name.upper()}", fill=(46, 204, 113))
505
- draw.text((40, 260), f"ALPHA RET: +{ret}%", fill=(255, 255, 255))
506
- draw.text((40, 360), f"SHARPE: {sharpe}", fill=(255, 255, 255))
507
- draw.text((40, 460), f"SESSION: {SESSION_ID}", fill=(100, 100, 110))
508
- draw.text((40, 720), "VERIFIED BY SENTINEL AI", fill=(46, 204, 113))
509
-
510
- fp = io.BytesIO()
511
- base.save(fp, 'PNG')
512
- fp.seek(0)
513
- await ctx.send(file=discord.File(fp, filename=f"Viral_{ctx.author.id}.png"))
514
- except Exception as e:
515
- logger.error(f"Render Error: {e}")
516
- await ctx.send(f"❌ 渲染引擎崩溃: `{e}`")
517
- finally:
518
- await m.delete()
519
 
520
  # ==============================================================================
521
- # 11. 系统守护与自动备份链 (Maintenance)
522
  # ==============================================================================
523
  @tasks.loop(hours=1)
524
  async def auto_backup():
525
  bk_engine.save_db()
526
  logger.info("💾 [System] Hourly Database Backup Executed.")
527
 
528
- @bot.event
529
- async def on_ready():
530
- if not auto_backup.is_running():
531
- auto_backup.start()
532
- await bot.change_presence(
533
- activity=discord.Activity(
534
- type=discord.ActivityType.watching,
535
- name="HuggingFace Node"
536
- )
537
- )
538
- logger.info(f"✅ [Online] BK-GTA V62.9-Titan Live. Session: {SESSION_ID}")
539
-
540
- @bot.event
541
- async def on_disconnect():
542
- logger.warning("⚠️ [Gateway] Disconnected. discord.py will auto-reconnect...")
543
-
544
- @bot.event
545
- async def on_resumed():
546
- logger.info("✅ [Gateway] Session resumed successfully.")
547
  # ==============================================================================
548
- # 12. 启动入口 — DNS 就绪探测 + 安全重试
549
  # ==============================================================================
550
- import socket
551
-
552
- def wait_for_network(host="discord.com", port=443, timeout=120):
553
- """
554
- 在容器启动后等待 DNS 解析就绪。
555
- HuggingFace Spaces 容器冷启动时网络栈可能延迟 10-30 秒。
556
- """
557
- start = time.time()
558
- attempt = 0
559
- while time.time() - start < timeout:
560
- attempt += 1
561
- try:
562
- socket.setdefaulttimeout(10)
563
- socket.getaddrinfo(host, port)
564
- logger.info(f"✅ [Network] DNS resolved {host} successfully (attempt {attempt})")
565
- return True
566
- except socket.gaierror as e:
567
- logger.warning(f"⏳ [Network] DNS not ready (attempt {attempt}): {e}")
568
- time.sleep(5)
569
- except Exception as e:
570
- logger.warning(f"⏳ [Network] Check failed (attempt {attempt}): {e}")
571
- time.sleep(5)
572
- logger.error(f"❌ [Network] DNS resolution failed after {timeout}s")
573
- return False
574
-
575
-
576
  def run_bot():
577
  """
578
- [核心修复]
579
- 1. bot.start() 替代 bot.run():避免子线程信号处理器崩溃
580
- 2. DNS 就绪探测:等待 HuggingFace 容器网络栈初始化完成
581
- 3. 安全重试:DNS 暂时失败时自动等待重连
582
  """
 
 
583
  if not DISCORD_TOKEN:
584
  logger.critical("❌ [FATAL] DISCORD_TOKEN is missing!")
585
  return
586
 
587
- # ====== 第一步:等待网络就绪 ======
588
- logger.info("🌐 [Bot Thread] Waiting for network/DNS to be ready...")
589
- if not wait_for_network():
590
- logger.critical("❌ [FATAL] Network never became available. Bot cannot start.")
591
- return
592
 
593
- # ====== 第二步:带重试的连接 ======
594
- max_retries = 5
595
  for attempt in range(1, max_retries + 1):
596
- logger.info(f"🚀 [Bot Thread] Connection attempt {attempt}/{max_retries}...")
 
 
 
 
 
 
 
 
 
 
 
597
 
598
  loop = asyncio.new_event_loop()
599
  asyncio.set_event_loop(loop)
600
 
601
  try:
 
 
 
 
 
 
 
 
602
  loop.run_until_complete(bot.start(DISCORD_TOKEN, reconnect=True))
603
- # 如果 bot.start() 正常返回(比如被手动 close),跳出循环
604
- break
605
 
606
  except discord.LoginFailure:
607
- logger.critical("❌ [FATAL] DISCORD_TOKEN is invalid! Check your token.")
608
- break # Token 错误不重试
609
 
610
  except discord.PrivilegedIntentsRequired:
611
  logger.critical(
612
  "❌ [FATAL] Privileged Intents not enabled!\n"
613
- "→ Discord Developer Portal → Bot tab\n"
614
  "→ Enable 'SERVER MEMBERS INTENT' and 'MESSAGE CONTENT INTENT'"
615
  )
616
- break # 权限错误不重试
617
 
618
- except (OSError, ConnectionError, asyncio.TimeoutError) as e:
619
- # 网络类错误:值得重试
620
- logger.error(f"⚠️ [Bot] Network error on attempt {attempt}: {e}")
621
 
622
  except Exception as e:
623
- logger.error(f"❌ [Bot] Unexpected error on attempt {attempt}: {e}")
624
  traceback.print_exc()
625
 
626
  finally:
627
- # 清理本次循环的事件循环
628
  try:
629
  pending = asyncio.all_tasks(loop)
630
  for task in pending:
@@ -638,89 +653,37 @@ def run_bot():
638
  finally:
639
  loop.close()
640
 
641
- # 重试等待(递增延迟)
642
- if attempt < max_retries:
643
- wait_time = min(30 * attempt, 120)
644
- logger.info(f"⏳ [Bot] Waiting {wait_time}s before retry...")
645
- time.sleep(wait_time)
646
-
647
- # 重试前再次确认 DNS
648
- if not wait_for_network(timeout=60):
649
- logger.error("❌ [Bot] Network still unavailable, retrying anyway...")
650
-
651
- # 重建 bot 实例(因为 close() 后无法复用)
652
- _rebuild_bot()
653
-
654
- logger.error("🔴 [Bot Thread] Bot has stopped after all attempts.")
655
-
656
-
657
- def _rebuild_bot():
658
- """
659
- 重建 bot 实例并重新绑定所有命令和事件。
660
- bot.close() 后内部 session 被销毁,必须创建新实例。
661
- """
662
- global bot
663
- old_commands = {cmd.name: cmd for cmd in bot.commands}
664
- old_listeners = {}
665
-
666
- # 保存已注册的事件监听器
667
- for event_name in ['on_ready', 'on_disconnect', 'on_resumed']:
668
- listener = getattr(bot, event_name, None)
669
- if listener:
670
- old_listeners[event_name] = listener
671
-
672
- # 创建新实例
673
- bot = commands.Bot(command_prefix='!', intents=intents)
674
-
675
- # 恢复命令
676
- for name, cmd in old_commands.items():
677
- try:
678
- bot.add_command(cmd)
679
- except Exception:
680
- pass
681
-
682
- # 重新注册事件
683
- @bot.event
684
- async def on_ready():
685
- if not auto_backup.is_running():
686
- auto_backup.start()
687
- await bot.change_presence(
688
- activity=discord.Activity(type=discord.ActivityType.watching, name="HuggingFace Node")
689
- )
690
- logger.info(f"✅ [Online] BK-GTA V62.9-Titan Live. Session: {SESSION_ID}")
691
-
692
- @bot.event
693
- async def on_disconnect():
694
- logger.warning("⚠️ [Gateway] Disconnected. discord.py will auto-reconnect...")
695
-
696
- @bot.event
697
- async def on_resumed():
698
- logger.info("✅ [Gateway] Session resumed successfully.")
699
-
700
- logger.info("🔄 [Bot] Instance rebuilt with all commands restored.")
701
 
702
 
703
  if __name__ == "__main__":
704
  logger.info("=" * 60)
705
- logger.info(" BK-GTA V62.9-Titan Starting...")
706
- logger.info(f" Token present: {'YES' if DISCORD_TOKEN else 'NO (!!)'}")
707
  logger.info(f" Token length: {len(DISCORD_TOKEN)} chars")
708
  logger.info(f" Gemini keys: {len(KEY_POOL)}")
709
  logger.info(f" DB path: {DB_PATH}")
710
  logger.info("=" * 60)
711
 
712
- # 1. 启动 Discord Bot 后台线程
713
- bot_thread = threading.Thread(target=run_bot, daemon=True)
714
- bot_thread.start()
715
- logger.info("📡 [Main] Bot thread launched.")
716
-
717
- # 2. 启动 Gradio 前台哨兵
718
- logger.info("📡 [Main] Starting Gradio on port 7860...")
719
- iface = gr.Interface(
720
- fn=get_status,
721
- inputs=None,
722
- outputs="json",
723
- title="BK-GTA Sentinel AI",
724
- description="Physical architecture monitor. Logic Integrity: Secure."
725
- )
726
- iface.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
 
16
  import io
17
  import time
18
  import threading
19
+ import socket
20
  from PIL import Image, ImageDraw, ImageFont
21
  import gradio as gr
22
 
 
30
  )
31
  logger = logging.getLogger("BK_GTA_ARCHITECT_TITAN")
32
 
33
+ # ==============================================================================
34
+ # 0.5 DNS 修复:注入可靠 DNS 服务器
35
+ # ==============================================================================
36
+ def fix_dns():
37
+ """
38
+ HuggingFace Spaces 容器的默认 DNS 解析器不稳定。
39
+ 注入 Google (8.8.8.8) 和 Cloudflare (1.1.1.1) DNS 作为备用。
40
+ """
41
+ dns_entries = "\nnameserver 8.8.8.8\nnameserver 1.1.1.1\n"
42
+ try:
43
+ # 先读取当前内容,避免重复添加
44
+ with open('/etc/resolv.conf', 'r') as f:
45
+ current = f.read()
46
+ if '8.8.8.8' not in current:
47
+ with open('/etc/resolv.conf', 'a') as f:
48
+ f.write(dns_entries)
49
+ logger.info("✅ [DNS] Injected Google & Cloudflare DNS servers.")
50
+ else:
51
+ logger.info("✅ [DNS] Reliable DNS servers already present.")
52
+ except PermissionError:
53
+ logger.warning("⚠️ [DNS] Cannot write /etc/resolv.conf (read-only). Trying res_init...")
54
+ # 备用方案:通过环境变量影响 DNS
55
+ os.environ['RES_OPTIONS'] = 'timeout:3 attempts:5'
56
+ except Exception as e:
57
+ logger.warning(f"⚠️ [DNS] Fix attempt failed: {e}")
58
+
59
+ # 启动时立即执行 DNS 修复
60
+ fix_dns()
61
+
62
  # ==============================================================================
63
  # 1. 物理层:Gradio 哨兵与探针 (Gradio Sentinel)
64
  # ==============================================================================
 
76
  return {
77
  "status": "BK-GTA-TITAN-ACTIVE",
78
  "logic_integrity": "100%",
79
+ "engine_version": "V63.0-DNS-Fix",
80
  "discord_bot": bot_status,
81
  "latency": latency,
82
  "server_time": now,
 
102
  intents = discord.Intents.default()
103
  intents.message_content = True
104
  intents.members = True
105
+
106
+ def create_bot():
107
+ """创建 Bot 实例的工厂函数"""
108
+ return commands.Bot(command_prefix='!', intents=intents)
109
+
110
+ bot = create_bot()
111
 
112
  audit_lock = asyncio.Lock()
113
  DB_PATH = '/data/bk_gta_v62_unified.json' if os.path.exists('/data') else 'bk_gta_v62_unified.json'
 
137
 
138
  def _init_schema(self):
139
  return {
140
+ "version": "63.0-Titan",
141
  "users": {},
142
  "factions": {
143
  "BULLS": {"score": 0, "emoji": "🐂", "wins": 0},
 
259
  return "ERROR: ALL_KEYS_EXHAUSTED"
260
 
261
  # ==============================================================================
262
+ # 5. 注册所有命令的函数 (Command Registration)
263
  # ==============================================================================
264
+ def register_commands(target_bot):
265
+ """将所有命令和事件注册到指定的 bot 实例上"""
266
+
267
+ # --- 清除已有命令,避免重复注册 ---
268
+ target_bot.remove_command('help')
269
+
270
+ @target_bot.command()
271
+ async def profile(ctx, member: discord.Member = None):
272
+ t = member or ctx.author
273
+ u = bk_engine.get_user(t.id)
274
+ elo = u['elo']
275
+
276
+ if elo < 1300: rank, color = "Iron 学徒 🗑️", 0x717d7e
277
+ elif elo < 1500: rank, color = "Bronze 资深 🥉", 0xcd7f32
278
+ elif elo < 1800: rank, color = "Silver 精英 🥈", 0xbdc3c7
279
+ elif elo < 2100: rank, color = "Gold 专家 🥇", 0xf1c40f
280
+ elif elo < 2500: rank, color = "Platinum 大师 💎", 0x3498db
281
+ else: rank, color = "Grandmaster 传奇 👑", 0x9b59b6
282
+
283
+ emb = discord.Embed(title=f"📊 Quant Profile: {t.display_name}", color=color)
284
+ emb.add_field(name="Rating (ELO)", value=f"**{elo}** ({rank})", inline=True)
285
+ emb.add_field(name="Assets", value=f"💰 **{u['points']}**", inline=True)
286
+
287
+ f_info = u['faction'] or "Free Agent"
288
+ f_emoji = bk_engine.data["factions"].get(f_info, {}).get("emoji", "🌐")
289
+ emb.add_field(name="Faction", value=f"{f_emoji} {f_info}", inline=True)
290
+
291
+ stats = (
292
+ f"Total Audits: {u['audits_count']}\n"
293
+ f"Peak Score: {u['highest_score']:.1f}\n"
294
+ f"Max MDD: {u.get('mdd_record',0):.2f}%"
295
+ )
296
+ emb.add_field(name="Institutional Metrics", value=f"```yaml\n{stats}\n```", inline=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
+ if target_avatar := t.avatar:
299
+ emb.set_thumbnail(url=target_avatar.url)
300
+ emb.set_footer(text=f"Node: HuggingFace Titan | Session: {SESSION_ID}")
301
+ await ctx.send(embed=emb)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
+ @target_bot.command()
304
+ async def leaderboard(ctx):
305
+ all_users = []
306
+ for uid, d in bk_engine.data["users"].items():
307
+ all_users.append({"id": uid, "elo": d["elo"], "score": d["highest_score"]})
308
+
309
+ top = sorted(all_users, key=lambda x: x["elo"], reverse=True)[:10]
310
+ desc = ""
311
+ for idx, u in enumerate(top):
312
+ icon = ["🥇","🥈","🥉"][idx] if idx < 3 else f"{idx+1}."
313
+ desc += f"{icon} <@{u['id']}> : **{u['elo']}** (Peak: {u['score']:.1f})\n"
314
+
315
+ emb = discord.Embed(title="🏆 BK-GTA Institutional Leaderboard", description=desc or "No data.", color=0xf1c40f)
316
+ f_info = ""
317
+ for k, v in bk_engine.data["factions"].items():
318
+ f_info += f"{v['emoji']} {k}: {v['score']} pts "
319
+ if f_info:
320
+ emb.add_field(name="⚔️ Faction War", value=f_info, inline=False)
321
+ await ctx.send(embed=emb)
322
 
323
+ @target_bot.command()
324
+ async def news(ctx):
325
+ if not ctx.message.attachments: return await ctx.send("❌ Error: 请上传情报截图。")
326
+ m = await ctx.send("📡 **[Sentinel] Analyzing Macro Pulse...**")
327
 
 
 
328
  prompt = (
329
+ "你是一名顶级对冲基金策略师。请解析截图内容:\n"
330
+ "1. 评分 (SCORE): -10 (极大利空) +10 (极大利多)。\n"
331
+ "2. 总结 (SUMMARY): 30字内概括核心变量。\n"
332
+ "3. 解读 (TAKE): 机构视角的隐含逻辑。\n"
333
+ "格式必须为:\nSCORE: [v]\nSUMMARY: [v]\nTAKE: [v]"
334
  )
 
335
  res = await call_gemini(prompt, ctx.message.attachments[0])
336
 
337
+ score = safe_extract_float(res, "SCORE")
338
+ summary = safe_extract_text(res, "SUMMARY", "TAKE")
339
+ take = safe_extract_text(res, "TAKE")
340
+
341
+ color = 0x2ecc71 if score > 0 else 0xe74c3c if score < 0 else 0xbdc3c7
342
+ emb = discord.Embed(title="📰 Macro Intelligence Analysis", color=color)
343
+ emb.add_field(name="Sentiment Score", value=f"**{score:+.1f} / 10**", inline=True)
344
+ emb.add_field(name="Core Catalyst", value=summary, inline=False)
345
+ emb.add_field(name="Institutional View", value=f"```\n{take}\n```", inline=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
  await m.delete()
348
  await ctx.send(embed=emb)
349
 
350
+ @target_bot.command()
351
+ async def evaluate(ctx):
352
+ if not ctx.message.attachments: return await ctx.send("❌ Error: 请上传回测或结算截图。")
353
+
354
+ if audit_lock.locked():
355
+ return await ctx.send("⚠️ **[System Busy]** 正在处理另一项审计,请稍后。")
356
+
357
+ async with audit_lock:
358
+ m = await ctx.send("🛡️ **[Audit Engine] 启动深度扫描... (锁闭模式)**")
359
+ prompt = (
360
+ "你是一名顶级量化风险官。请提取并审计以下指标:\n"
361
+ "RET: 收益率, MDD: 最大回撤, PF: 盈利因子, SHARPE: 夏普比率, TRADES: 总笔数, S_BASE: 基础评分(0-100)。\n"
362
+ " MDD > 35% 或交易笔数 < 10,则 VIOLATION 为 TRUE。\n"
363
+ "H_ZH: 中文核心优势或致命风险概括。\n"
364
+ "格式:\nRET: [v]\nMDD: [v]\nPF: [v]\nSHARPE: [v]\nTRADES: [v]\nS_BASE: [v]\nVIOLATION: [TRUE/FALSE]\nH_ZH: [v]"
365
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
367
+ res = await call_gemini(prompt, ctx.message.attachments[0])
368
+
369
+ if "ERROR" in res:
370
+ await m.delete()
371
+ return await ctx.send("🔴 [Audit Crash] 引擎请求失败。")
372
+
373
+ ret = safe_extract_float(res, "RET")
374
+ mdd = safe_extract_float(res, "MDD")
375
+ sharpe = safe_extract_float(res, "SHARPE")
376
+ trades = int(safe_extract_float(res, "TRADES"))
377
+ s_base = safe_extract_float(res, "S_BASE")
378
+ violation = "TRUE" in res.upper()
379
+
380
+ if violation or mdd > 35.0:
381
+ s_final = 0.0
382
+ status = "❌ REJECTED (High Risk)"
383
+ color = 0xe74c3c
384
+ else:
385
+ weight_sample = math.log10(max(trades, 1)) / 1.5
386
+ weight_risk = (sharpe / 2.0) if sharpe > 0 else 0.5
387
+ s_final = min(max(s_base * weight_sample * weight_risk, 0.0), 100.0)
388
+ status = " AUDIT CLEARED"
389
+ color = 0x2ecc71
390
+
391
+ bk_engine.update_audit(ctx.author.id, s_final, mdd, sharpe)
392
+ highlights = safe_extract_text(res, "H_ZH")
393
+
394
+ emb = discord.Embed(title="🛡️ Quantitative Audit Report", color=color)
395
+ emb.add_field(name="Verdict", value=f"**{status}**", inline=False)
396
+ metrics = f"Return: {ret}% | MDD: {mdd}%\nSharpe: {sharpe} | Trades: {trades}"
397
+ emb.add_field(name="Key Metrics", value=f"```\n{metrics}\n```", inline=False)
398
+ emb.add_field(name="Logic Score", value=f"**{s_final:.2f} / 100**", inline=True)
399
+ emb.add_field(name="Highlights", value=highlights, inline=False)
400
 
401
+ await m.delete()
402
+ await ctx.send(embed=emb)
403
+
404
+ @target_bot.command()
405
+ @commands.has_permissions(administrator=True)
406
+ async def open_bet(ctx, *, topic):
407
+ bid = str(random.randint(100, 999))
408
+ bk_engine.active_bets[bid] = {
409
+ "topic": topic, "up": 0, "down": 0,
410
+ "u_up": {}, "u_down": {}, "status": "OPEN",
411
+ "time": str(datetime.datetime.now())
412
+ }
413
+ await ctx.send(f"🎰 **[Bet-ID:{bid}]** 新博弈盘口已开启!\n���题:**{topic}**\n输入 `!bet {bid} UP/DOWN [金额]` 参与。")
414
+
415
+ @target_bot.command()
416
+ async def bet(ctx, bid: str, side: str, amt: int):
417
+ if bid not in bk_engine.active_bets or bk_engine.active_bets[bid]["status"] != "OPEN":
418
+ return await ctx.send("❌ 盘口不存在或已关闭。")
419
+
420
+ u = bk_engine.get_user(ctx.author.id)
421
+ if u["points"] < amt or amt <= 0:
422
+ return await ctx.send("❌ 资产点数不足。")
423
+
424
+ side = side.upper()
425
+ if side not in ["UP", "DOWN"]: return await ctx.send("❌ 必须选择 UP 或 DOWN。")
426
+
427
+ u["points"] -= amt
428
+ b = bk_engine.active_bets[bid]
429
+ pool = "u_up" if side == "UP" else "u_down"
430
+ b[side.lower()] += amt
431
+ b[pool][str(ctx.author.id)] = b[pool].get(str(ctx.author.id), 0) + amt
432
+
433
+ bk_engine.save_db()
434
+ await ctx.send(f"✅ 已确认:{ctx.author.name} 向 {side} 注入 {amt} 点资产。")
435
+
436
+ @target_bot.command()
437
+ @commands.has_permissions(administrator=True)
438
+ async def settle_bet(ctx, bid: str, winner: str):
439
+ if bid not in bk_engine.active_bets: return
440
+ b = bk_engine.active_bets[bid]
441
+ winner = winner.upper()
442
+
443
+ win_pool = b["u_up"] if winner == "UP" else b["u_down"]
444
+ total_w = b["up"] if winner == "UP" else b["down"]
445
+ total_l = b["down"] if winner == "UP" else b["up"]
446
+
447
+ if total_w > 0:
448
+ for uid, amt in win_pool.items():
449
+ profit = (amt / total_w) * total_l
450
+ bk_engine.get_user(int(uid))["points"] += int(amt + profit)
451
+
452
+ b["status"] = "SETTLED"
453
+ bk_engine.save_db()
454
+ await ctx.send(f"🏁 盘口 {bid} 已结算!胜利方:**{winner}**。资产已自动划转。")
455
+
456
+ @target_bot.command()
457
+ async def challenge(ctx, member: discord.Member):
458
+ if member.id == ctx.author.id: return
459
+ did = f"D-{random.randint(1000, 9999)}"
460
+ bk_engine.pending_challenges[member.id] = {"challenger": ctx.author.id, "id": did}
461
+ await ctx.send(f"⚔️ {ctx.author.name} 向 {member.mention} 发起挑战!\nID: `{did}`. 输入 `!accept` 接受。")
462
+
463
+ @target_bot.command()
464
+ async def accept(ctx):
465
+ if ctx.author.id not in bk_engine.pending_challenges:
466
+ return await ctx.send("❌ 没有待处理的决斗请求。")
467
+
468
+ req = bk_engine.pending_challenges.pop(ctx.author.id)
469
+ did = req["id"]
470
+ bk_engine.active_duels[did] = {
471
+ "p1": req["challenger"], "p2": ctx.author.id,
472
+ "s1": None, "s2": None, "t": str(datetime.datetime.now())
473
+ }
474
+ await ctx.send(f"🔥 **竞技场锁定!** 决斗 ID: `{did}`\n请双方通过 `!duel_submit {did}` 提交审计截图。")
475
+
476
+ @target_bot.command()
477
+ async def duel_submit(ctx, did: str):
478
+ if did not in bk_engine.active_duels: return
479
+ if not ctx.message.attachments: return
480
+
481
+ duel = bk_engine.active_duels[did]
482
+ if ctx.author.id not in [duel["p1"], duel["p2"]]: return
483
+
484
+ m = await ctx.send("🔍 正在审计决斗数据...")
485
+ res = await call_gemini("Extract SCORE:[0-100]. Format: SCORE:[v]", ctx.message.attachments[0])
486
+ score = safe_extract_float(res, "SCORE")
487
+
488
+ if ctx.author.id == duel["p1"]: duel["s1"] = score
489
+ else: duel["s2"] = score
490
+
491
+ if duel["s1"] is not None and duel["s2"] is not None:
492
+ p1, p2 = duel["p1"], duel["p2"]
493
+ s1, s2 = duel["s1"], duel["s2"]
494
+ wid, lid = (p1, p2) if s1 > s2 else (p2, p1)
495
+ w_elo, l_elo, streak = bk_engine.update_elo(wid, lid)
496
+
497
+ emb = discord.Embed(title="🏆 Duel Arena Verdict", color=0xf1c40f)
498
+ emb.add_field(name="Winner", value=f"<@{wid}>\nScore: {max(s1,s2)}\nELO: {w_elo}", inline=True)
499
+ emb.add_field(name="Loser", value=f"<@{lid}>\nScore: {min(s1,s2)}\nELO: {l_elo}", inline=True)
500
+ emb.set_footer(text=f"Win Streak: {streak} | Duel ID: {did}")
501
+ await ctx.send(embed=emb)
502
+ del bk_engine.active_duels[did]
503
+ else:
504
+ await ctx.send(f"✅ 交易员 <@{ctx.author.id}> 分数已锁定 ({score})。等待对手提交。")
505
+ await m.delete()
506
 
507
+ @target_bot.command()
508
+ async def viral_audit(ctx):
509
+ if not ctx.message.attachments: return
510
+ m = await ctx.send("🎨 **[GPU-Node] 正在渲染视觉海报...**")
511
 
512
+ p = "Extract RET: [v] and SHARPE: [v]. Format: RET: [v] SHARPE: [v]"
513
+ res = await call_gemini(p, ctx.message.attachments[0])
514
+ ret, sharpe = safe_extract_float(res, "RET"), safe_extract_float(res, "SHARPE")
515
 
516
+ try:
517
+ W, H = 600, 800
518
+ base = Image.new('RGB', (W, H), (18, 20, 30))
519
+ draw = ImageDraw.Draw(base)
520
+
521
+ for i in range(0, W, 40): draw.line([(i, 0), (i, H)], fill=(30, 35, 45), width=1)
522
+ for j in range(0, H, 40): draw.line([(0, j), (W, j)], fill=(30, 35, 45), width=1)
523
+
524
+ draw.rectangle([10, 10, 590, 790], outline=(46, 204, 113), width=2)
525
+ draw.rectangle([20, 20, 580, 780], outline=(46, 204, 113), width=5)
526
+
527
+ draw.text((40, 60), "BK-GTA QUANTITATIVE SYSTEM", fill=(255, 255, 255))
528
+ draw.text((40, 150), f"TRADER: {ctx.author.name.upper()}", fill=(46, 204, 113))
529
+ draw.text((40, 260), f"ALPHA RET: +{ret}%", fill=(255, 255, 255))
530
+ draw.text((40, 360), f"SHARPE: {sharpe}", fill=(255, 255, 255))
531
+ draw.text((40, 460), f"SESSION: {SESSION_ID}", fill=(100, 100, 110))
532
+ draw.text((40, 720), "VERIFIED BY SENTINEL AI", fill=(46, 204, 113))
533
+
534
+ fp = io.BytesIO()
535
+ base.save(fp, 'PNG')
536
+ fp.seek(0)
537
+ await ctx.send(file=discord.File(fp, filename=f"Viral_{ctx.author.id}.png"))
538
+ except Exception as e:
539
+ logger.error(f"Render Error: {e}")
540
+ await ctx.send(f"❌ 渲染引擎崩溃: `{e}`")
541
+ finally:
542
+ await m.delete()
543
 
544
+ # --- 事件注册 ---
545
+ @target_bot.event
546
+ async def on_ready():
547
+ if not auto_backup.is_running():
548
+ auto_backup.start()
549
+ await target_bot.change_presence(
550
+ activity=discord.Activity(type=discord.ActivityType.watching, name="HuggingFace Node")
551
+ )
552
+ logger.info(f"✅ [Online] BK-GTA V63.0-Titan Live. Session: {SESSION_ID}")
553
 
554
+ @target_bot.event
555
+ async def on_disconnect():
556
+ logger.warning("⚠️ [Gateway] Disconnected. discord.py will auto-reconnect...")
 
 
 
 
 
 
557
 
558
+ @target_bot.event
559
+ async def on_resumed():
560
+ logger.info("✅ [Gateway] Session resumed successfully.")
 
 
 
 
561
 
562
+ logger.info(f"📋 [Init] Registered {len(target_bot.commands)} commands to bot.")
 
 
563
 
564
+ # 注册命令到初始 bot
565
+ register_commands(bot)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
 
567
  # ==============================================================================
568
+ # 6. 系统守护与自动备份 (Maintenance)
569
  # ==============================================================================
570
  @tasks.loop(hours=1)
571
  async def auto_backup():
572
  bk_engine.save_db()
573
  logger.info("💾 [System] Hourly Database Backup Executed.")
574
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
  # ==============================================================================
576
+ # 7. 启动入口 — DNS 修复 + 安全重试
577
  # ==============================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  def run_bot():
579
  """
580
+ 核心修复:
581
+ 1. fix_dns() 注入可靠 DNS (Google/Cloudflare)
582
+ 2. bot.start() 替代 bot.run() (子线程安全)
583
+ 3. 网络失败时自动重试, 重建 bot 实例
584
  """
585
+ global bot
586
+
587
  if not DISCORD_TOKEN:
588
  logger.critical("❌ [FATAL] DISCORD_TOKEN is missing!")
589
  return
590
 
591
+ max_retries = 10
 
 
 
 
592
 
 
 
593
  for attempt in range(1, max_retries + 1):
594
+ logger.info(f"🚀 [Bot] Connection attempt {attempt}/{max_retries}...")
595
+
596
+ # 每次重试前等待网络稳定
597
+ if attempt > 1:
598
+ wait_time = min(30 * attempt, 180)
599
+ logger.info(f"⏳ [Bot] Waiting {wait_time}s for network to stabilize...")
600
+ time.sleep(wait_time)
601
+
602
+ # 重建 bot 实例 (close 后不可复用)
603
+ logger.info("🔄 [Bot] Rebuilding bot instance...")
604
+ bot = create_bot()
605
+ register_commands(bot)
606
 
607
  loop = asyncio.new_event_loop()
608
  asyncio.set_event_loop(loop)
609
 
610
  try:
611
+ # 先做一次快速 DNS 测试 (非阻塞式, 失败也继续)
612
+ try:
613
+ socket.setdefaulttimeout(10)
614
+ addr = socket.getaddrinfo("discord.com", 443)
615
+ logger.info(f"✅ [DNS] discord.com resolved: {addr[0][4]}")
616
+ except Exception as dns_err:
617
+ logger.warning(f"⚠️ [DNS] Pre-check failed: {dns_err} — will try connecting anyway")
618
+
619
  loop.run_until_complete(bot.start(DISCORD_TOKEN, reconnect=True))
620
+ break # 正常退出则跳出
 
621
 
622
  except discord.LoginFailure:
623
+ logger.critical("❌ [FATAL] Invalid DISCORD_TOKEN!")
624
+ break # 不重试
625
 
626
  except discord.PrivilegedIntentsRequired:
627
  logger.critical(
628
  "❌ [FATAL] Privileged Intents not enabled!\n"
629
+ "→ https://discord.com/developers/applications → Bot tab\n"
630
  "→ Enable 'SERVER MEMBERS INTENT' and 'MESSAGE CONTENT INTENT'"
631
  )
632
+ break # 不重试
633
 
634
+ except (OSError, ConnectionError) as e:
635
+ logger.error(f"🌐 [Bot] Network error (attempt {attempt}): {e}")
636
+ # 网络错误值得重试
637
 
638
  except Exception as e:
639
+ logger.error(f"❌ [Bot] Error (attempt {attempt}): {e}")
640
  traceback.print_exc()
641
 
642
  finally:
 
643
  try:
644
  pending = asyncio.all_tasks(loop)
645
  for task in pending:
 
653
  finally:
654
  loop.close()
655
 
656
+ logger.error("🔴 [Bot Thread] Bot startup exhausted all retries.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657
 
658
 
659
  if __name__ == "__main__":
660
  logger.info("=" * 60)
661
+ logger.info(" BK-GTA V63.0-Titan Starting...")
662
+ logger.info(f" Token present: {'YES' if DISCORD_TOKEN else '!! NO !!'}")
663
  logger.info(f" Token length: {len(DISCORD_TOKEN)} chars")
664
  logger.info(f" Gemini keys: {len(KEY_POOL)}")
665
  logger.info(f" DB path: {DB_PATH}")
666
  logger.info("=" * 60)
667
 
668
+ # 1. 先启动 Gradio (让 HuggingFace 认为服务已就绪)
669
+ # Gradio 在后台线程运行, Bot 在主逻辑中延迟启动
670
+ def run_gradio():
671
+ iface = gr.Interface(
672
+ fn=get_status,
673
+ inputs=None,
674
+ outputs="json",
675
+ title="BK-GTA Sentinel AI",
676
+ description="Physical architecture monitor. Logic Integrity: Secure."
677
+ )
678
+ iface.launch(server_name="0.0.0.0", server_port=7860)
679
+
680
+ gradio_thread = threading.Thread(target=run_gradio, daemon=True)
681
+ gradio_thread.start()
682
+ logger.info("📡 [Main] Gradio launched on port 7860.")
683
+
684
+ # 2. 等待容器网络完全就绪 (关键!)
685
+ logger.info("⏳ [Main] Waiting 10s for container network to initialize...")
686
+ time.sleep(10)
687
+
688
+ # 3. 在主线程运行 Bot (避免子线程信号问题)
689
+ run_bot()