-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
155 lines (128 loc) · 4.4 KB
/
app.py
File metadata and controls
155 lines (128 loc) · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""
RickBot 2.0 - Application Entry Point
Production-grade Discord bot with graceful shutdown and structured logging.
"""
import asyncio
import signal
import sys
import warnings
from pathlib import Path
import logging
import time
from dotenv import load_dotenv
from core import RickBot, Config
from helpers.logger import (
ColoredFormatter,
print_startup_banner,
print_box,
format_duration,
print_checkmark,
)
from helpers.rickbot import get_goodbye_message
# Configure colored logging
handler = logging.StreamHandler()
formatter = ColoredFormatter(
"[{asctime}] [{levelname}] {name}: {message}",
datefmt="%Y-%m-%d %H:%M:%S",
style="{",
)
handler.setFormatter(formatter)
# Configure root logger
logging.root.handlers.clear()
logging.root.addHandler(handler)
logging.root.setLevel(logging.INFO)
# Suppress asyncio ResourceWarnings (harmless Discord.py cleanup warnings)
warnings.filterwarnings("ignore", category=ResourceWarning, message=".*unclosed.*")
logger = logging.getLogger(__name__)
async def main() -> None:
"""
Main application entry point.
Handles:
- Configuration loading
- Bot initialization
- Graceful shutdown on signals
"""
# Track total startup time
startup_start = time.time()
# Print startup banner
print_startup_banner("RickBot 2.0")
# Change to script directory
script_dir = Path(__file__).parent
import os
os.chdir(script_dir)
# Load environment variables
load_dotenv()
print_checkmark("Environment variables loaded", success=True)
# Check if config exists, create template if not
config_path = Path("config.yaml")
if not config_path.exists():
logger.warning("Config file not found, creating template...")
Config.save_template(config_path)
logger.error(
"Configuration template created at config.yaml\n"
"Please edit this file and set your environment variables before starting the bot."
)
sys.exit(1)
# Load configuration
try:
config = Config.load(config_path)
print_checkmark("Configuration loaded successfully", success=True)
except Exception as e:
print_checkmark(f"Failed to load configuration: {e}", success=False)
logger.error(f"Failed to load configuration: {e}", exc_info=True)
sys.exit(1)
# Configure logging level from config
logging.getLogger().setLevel(logging.getLevelName(config.logging.level))
if config.logging.log_discord_library:
logging.getLogger("discord").setLevel(logging.DEBUG)
else:
logging.getLogger("discord").setLevel(logging.WARNING)
logging.getLogger("discord.http").setLevel(logging.WARNING)
# Initialize bot
bot = RickBot(config)
# Setup graceful shutdown
loop = asyncio.get_running_loop()
def signal_handler(sig: signal.Signals) -> None:
"""Handle shutdown signals"""
logger.info(f"Received signal {sig.name}, shutting down gracefully...")
asyncio.create_task(bot.close())
# Register signal handlers (Unix only)
if sys.platform != "win32":
for sig in (signal.SIGTERM, signal.SIGINT):
loop.add_signal_handler(sig, lambda s=sig: signal_handler(s))
else:
# Windows doesn't support add_signal_handler
signal.signal(signal.SIGINT, lambda s, f: signal_handler(signal.SIGINT))
# Run bot
try:
print() # Empty line before starting
logger.debug("Starting RickBot...")
await bot.start(config.bot.token)
except KeyboardInterrupt:
print() # Empty line before shutdown message
logger.info("Keyboard interrupt received")
except Exception as e:
print() # Empty line before error
logger.error(f"Fatal error: {e}", exc_info=True)
sys.exit(1)
finally:
if not bot.is_closed():
await bot.close()
# Calculate total runtime
total_runtime = time.time() - startup_start
# Print shutdown box
print()
shutdown_content = [
f"Total runtime: {format_duration(total_runtime)}",
"All systems shut down gracefully",
"",
get_goodbye_message(),
]
print_box("RickBot Shutdown Complete", shutdown_content, color="magenta")
print()
if __name__ == "__main__":
"""Entry point"""
try:
asyncio.run(main())
except KeyboardInterrupt:
pass # Handled in main()