Spaces:
No application file
No application file
Update app.py
Browse files
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 |
-
[核心修复]
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
"""
|
| 558 |
if not DISCORD_TOKEN:
|
| 559 |
-
logger.critical("❌ [FATAL] DISCORD_TOKEN is missing!
|
| 560 |
return
|
| 561 |
|
| 562 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
|
| 564 |
-
# ======
|
| 565 |
-
|
| 566 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 599 |
finally:
|
| 600 |
-
|
| 601 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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,
|