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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +143 -47
app.py CHANGED
@@ -544,61 +544,160 @@ async def on_disconnect():
544
  @bot.event
545
  async def on_resumed():
546
  logger.info("✅ [Gateway] Session resumed successfully.")
547
-
548
  # ==============================================================================
549
- # 12. 启动入口 — 关键修复区域
550
  # ==============================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
  def run_bot():
552
  """
553
- [核心修复] bot.run() 内部调用 asyncio.run(),会注册信号处理器,
554
- 信号处理器只能在主线程注册。在子线程中必须:
555
- 1. 手动创建事件循环 asyncio.new_event_loop()
556
- 2. 使用 bot.start()(协程版本)而非 bot.run()
557
  """
558
  if not DISCORD_TOKEN:
559
- logger.critical("❌ [FATAL] DISCORD_TOKEN is missing! Set it in HuggingFace Secrets.")
560
  return
561
 
562
- logger.info("🚀 [Bot Thread] Initializing Discord connection...")
 
 
 
 
563
 
564
- # ====== 关键为子线程创建专属事件循环 ======
565
- loop = asyncio.new_event_loop()
566
- asyncio.set_event_loop(loop)
 
 
 
 
567
 
568
- try:
569
- # bot.start() 是纯协程,不会触发信号注册
570
- loop.run_until_complete(bot.start(DISCORD_TOKEN, reconnect=True))
571
- except discord.LoginFailure:
572
- logger.critical(
573
- "❌ [FATAL] DISCORD_TOKEN is invalid!\n"
574
- "Go to https://discord.com/developers/applications → Bot → Reset Token"
575
- )
576
- except discord.PrivilegedIntentsRequired:
577
- logger.critical(
578
- "❌ [FATAL] Privileged Intents not enabled!\n"
579
- "Go to Discord Developer Portal → Bot tab →\n"
580
- "Enable 'SERVER MEMBERS INTENT' and 'MESSAGE CONTENT INTENT'"
581
- )
582
- except Exception as e:
583
- logger.error(f"❌ [Bot Fatal Error] {e}")
584
- traceback.print_exc()
585
- finally:
586
- logger.warning("🔴 [Bot Thread] Event loop ending, cleaning up...")
587
  try:
588
- # 清理所有未完成的任务
589
- pending = asyncio.all_tasks(loop)
590
- for task in pending:
591
- task.cancel()
592
- # 等待取消完成
593
- if pending:
594
- loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
595
- if not bot.is_closed():
596
- loop.run_until_complete(bot.close())
597
- except Exception as cleanup_err:
598
- logger.error(f"Cleanup error: {cleanup_err}")
 
 
 
 
 
 
 
 
 
 
 
 
 
599
  finally:
600
- loop.close()
601
- logger.error("🔴 [Bot Thread] Discord bot has stopped.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
 
603
 
604
  if __name__ == "__main__":
@@ -615,10 +714,7 @@ if __name__ == "__main__":
615
  bot_thread.start()
616
  logger.info("📡 [Main] Bot thread launched.")
617
 
618
- # 2. 给 bot 线程一点启动时间
619
- time.sleep(3)
620
-
621
- # 3. 启动 Gradio 前台哨兵 (占据主线程)
622
  logger.info("📡 [Main] Starting Gradio on port 7860...")
623
  iface = gr.Interface(
624
  fn=get_status,
 
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:
631
+ task.cancel()
632
+ if pending:
633
+ loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
634
+ if not bot.is_closed():
635
+ loop.run_until_complete(bot.close())
636
+ except Exception:
637
+ pass
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__":
 
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,