252 lines
9.6 KiB
Python
252 lines
9.6 KiB
Python
import threading
|
|
import discord
|
|
from discord.ext import commands
|
|
import os
|
|
from dotenv import load_dotenv
|
|
import sys
|
|
import asyncio
|
|
import subprocess
|
|
import importlib.util
|
|
from commands import load_all_cogs, reload_all_cogs
|
|
from error_handler import handle_error, patch_discord_methods, store_interaction_content
|
|
from utils import reload_script
|
|
|
|
# Import the unified API service runner
|
|
import sys
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
from discordbot.run_unified_api import start_api_in_thread
|
|
|
|
# Check if API dependencies are available
|
|
try:
|
|
import uvicorn
|
|
API_AVAILABLE = True
|
|
except ImportError:
|
|
print("uvicorn not available. API service will not be available.")
|
|
API_AVAILABLE = False
|
|
|
|
# Load environment variables from .env file
|
|
load_dotenv()
|
|
|
|
# Set up intents (permissions)
|
|
intents = discord.Intents.default()
|
|
intents.message_content = True
|
|
intents.members = True
|
|
|
|
# Create bot instance with command prefix '!' and enable the application commands
|
|
bot = commands.Bot(command_prefix='!', intents=intents)
|
|
bot.owner_id = int(os.getenv('OWNER_USER_ID'))
|
|
|
|
@bot.event
|
|
async def on_ready():
|
|
print(f'{bot.user.name} has connected to Discord!')
|
|
print(f'Bot ID: {bot.user.id}')
|
|
# Set the bot's status
|
|
await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name="!help"))
|
|
print("Bot status set to 'Listening to !help'")
|
|
|
|
# Patch Discord methods to store message content
|
|
try:
|
|
patch_discord_methods()
|
|
print("Discord methods patched to store message content for error handling")
|
|
|
|
# Make the store_interaction_content function available globally
|
|
import builtins
|
|
builtins.store_interaction_content = store_interaction_content
|
|
print("Made store_interaction_content available globally")
|
|
except Exception as e:
|
|
print(f"Warning: Failed to patch Discord methods: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
try:
|
|
print("Starting command sync process...")
|
|
# List commands before sync
|
|
commands_before = [cmd.name for cmd in bot.tree.get_commands()]
|
|
print(f"Commands before sync: {commands_before}")
|
|
|
|
# Perform sync
|
|
synced = await bot.tree.sync()
|
|
print(f"Synced {len(synced)} command(s)")
|
|
|
|
# List commands after sync
|
|
commands_after = [cmd.name for cmd in bot.tree.get_commands()]
|
|
print(f"Commands after sync: {commands_after}")
|
|
|
|
except Exception as e:
|
|
print(f"Failed to sync commands: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
@bot.event
|
|
async def on_shard_disconnect(shard_id):
|
|
print(f"Shard {shard_id} disconnected. Attempting to reconnect...")
|
|
try:
|
|
await bot.connect(reconnect=True)
|
|
print(f"Shard {shard_id} reconnected successfully.")
|
|
except Exception as e:
|
|
print(f"Failed to reconnect shard {shard_id}: {e}")
|
|
|
|
# Error handling
|
|
@bot.event
|
|
async def on_command_error(ctx, error):
|
|
await handle_error(ctx, error)
|
|
|
|
@bot.tree.error
|
|
async def on_app_command_error(interaction, error):
|
|
await handle_error(interaction, error)
|
|
|
|
@commands.command(name="restart", help="Restarts the bot. Owner only.")
|
|
@commands.is_owner()
|
|
async def restart(ctx):
|
|
"""Restarts the bot. (Owner Only)"""
|
|
await ctx.send("Restarting the bot...")
|
|
await bot.close() # Gracefully close the bot
|
|
os.execv(sys.executable, [sys.executable] + sys.argv) # Restart the bot process
|
|
|
|
bot.add_command(restart)
|
|
|
|
@commands.command(name="gitpull_restart", help="Pulls latest code from git and restarts the bot. Owner only.")
|
|
@commands.is_owner()
|
|
async def gitpull_restart(ctx):
|
|
"""Pulls latest code from git and restarts the bot. (Owner Only)"""
|
|
await ctx.send("Pulling latest code from git...")
|
|
proc = await asyncio.create_subprocess_exec(
|
|
"git", "pull",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE
|
|
)
|
|
stdout, stderr = await proc.communicate()
|
|
output = stdout.decode().strip() + "\n" + stderr.decode().strip()
|
|
if "unstaged changes" in output or "Please commit your changes" in output:
|
|
await ctx.send("Unstaged changes detected. Committing changes before pulling...")
|
|
commit_proc = await asyncio.create_subprocess_exec(
|
|
"git", "commit", "-am", "Git pull and restart command",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE
|
|
)
|
|
commit_stdout, commit_stderr = await commit_proc.communicate()
|
|
commit_output = commit_stdout.decode().strip() + "\n" + commit_stderr.decode().strip()
|
|
await ctx.send(f"Committed changes:\n```\n{commit_output}\n```Trying git pull again...")
|
|
proc = await asyncio.create_subprocess_exec(
|
|
"git", "pull",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE
|
|
)
|
|
stdout, stderr = await proc.communicate()
|
|
output = stdout.decode().strip() + "\n" + stderr.decode().strip()
|
|
if proc.returncode == 0:
|
|
await ctx.send(f"Git pull successful:\n```\n{output}\n```Restarting the bot...")
|
|
await bot.close()
|
|
os.execv(sys.executable, [sys.executable] + sys.argv)
|
|
else:
|
|
await ctx.send(f"Git pull failed:\n```\n{output}\n```")
|
|
|
|
bot.add_command(gitpull_restart)
|
|
|
|
@commands.command(name="reload_cogs", help="Reloads all cogs. Owner only.")
|
|
@commands.is_owner()
|
|
async def reload_cogs(ctx):
|
|
"""Reloads all cogs. (Owner Only)"""
|
|
await ctx.send("Reloading all cogs...")
|
|
reloaded_cogs, failed_reload = await reload_all_cogs(bot)
|
|
if reloaded_cogs:
|
|
await ctx.send(f"Successfully reloaded cogs: {', '.join(reloaded_cogs)}")
|
|
if failed_reload:
|
|
await ctx.send(f"Failed to reload cogs: {', '.join(failed_reload)}")
|
|
|
|
bot.add_command(reload_cogs)
|
|
|
|
@commands.command(name="gitpull_reload", help="Pulls latest code from git and reloads all cogs. Owner only.")
|
|
@commands.is_owner()
|
|
async def gitpull_reload(ctx):
|
|
"""Pulls latest code from git and reloads all cogs. (Owner Only)"""
|
|
await ctx.send("Pulling latest code from git...")
|
|
proc = await asyncio.create_subprocess_exec(
|
|
"git", "pull",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE
|
|
)
|
|
stdout, stderr = await proc.communicate()
|
|
output = stdout.decode().strip() + "\n" + stderr.decode().strip()
|
|
if "unstaged changes" in output or "Please commit your changes" in output:
|
|
await ctx.send("Unstaged changes detected. Committing changes before pulling...")
|
|
commit_proc = await asyncio.create_subprocess_exec(
|
|
"git", "commit", "-am", "Git pull and reload command",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE
|
|
)
|
|
commit_stdout, commit_stderr = await commit_proc.communicate()
|
|
commit_output = commit_stdout.decode().strip() + "\n" + commit_stderr.decode().strip()
|
|
await ctx.send(f"Committed changes:\n```\n{commit_output}\n```Trying git pull again...")
|
|
proc = await asyncio.create_subprocess_exec(
|
|
"git", "pull",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE
|
|
)
|
|
stdout, stderr = await proc.communicate()
|
|
output = stdout.decode().strip() + "\n" + stderr.decode().strip()
|
|
if proc.returncode == 0:
|
|
await ctx.send(f"Git pull successful:\n```\n{output}\n```Reloading all cogs...")
|
|
reloaded_cogs, failed_reload = await reload_all_cogs(bot)
|
|
if reloaded_cogs:
|
|
await ctx.send(f"Successfully reloaded cogs: {', '.join(reloaded_cogs)}")
|
|
if failed_reload:
|
|
await ctx.send(f"Failed to reload cogs: {', '.join(failed_reload)}")
|
|
else:
|
|
await ctx.send(f"Git pull failed:\n```\n{output}\n```")
|
|
|
|
bot.add_command(gitpull_reload)
|
|
|
|
|
|
|
|
|
|
# The unified API service is now handled by run_unified_api.py
|
|
|
|
async def main():
|
|
"""Main async function to load cogs and start the bot."""
|
|
TOKEN = os.getenv('DISCORD_TOKEN')
|
|
if not TOKEN:
|
|
raise ValueError("No token found. Make sure to set DISCORD_TOKEN in your .env file.")
|
|
|
|
# Start Flask server as a separate process
|
|
flask_process = subprocess.Popen([sys.executable, "flask_server.py"], cwd=os.path.dirname(__file__))
|
|
|
|
# Start the unified API service in a separate thread if available
|
|
api_thread = None
|
|
if API_AVAILABLE:
|
|
print("Starting unified API service...")
|
|
try:
|
|
# Start the API in a separate thread
|
|
api_thread = start_api_in_thread()
|
|
print("Unified API service started successfully")
|
|
except Exception as e:
|
|
print(f"Failed to start unified API service: {e}")
|
|
|
|
# Configure OAuth settings from environment variables
|
|
oauth_host = os.getenv("OAUTH_HOST", "0.0.0.0")
|
|
oauth_port = int(os.getenv("OAUTH_PORT", "8080"))
|
|
oauth_redirect_uri = os.getenv("DISCORD_REDIRECT_URI", f"http://{oauth_host}:{oauth_port}/oauth/callback")
|
|
|
|
# Update the OAuth redirect URI in the environment
|
|
os.environ["DISCORD_REDIRECT_URI"] = oauth_redirect_uri
|
|
print(f"OAuth redirect URI set to: {oauth_redirect_uri}")
|
|
|
|
try:
|
|
async with bot:
|
|
# Load all cogs from the 'cogs' directory
|
|
await load_all_cogs(bot)
|
|
# Start the bot using start() for async context
|
|
await bot.start(TOKEN)
|
|
finally:
|
|
# Terminate the Flask server process when the bot stops
|
|
flask_process.terminate()
|
|
print("Flask server process terminated.")
|
|
|
|
# Run the main async function
|
|
if __name__ == '__main__':
|
|
try:
|
|
asyncio.run(main())
|
|
except KeyboardInterrupt:
|
|
print("Bot stopped by user.")
|
|
except Exception as e:
|
|
print(f"An error occurred running the bot: {e}")
|