fixed, fuully bug free
This commit is contained in:
145
bot.py
145
bot.py
@@ -4,6 +4,8 @@ import os
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from database import Database
|
from database import Database
|
||||||
import logging
|
import logging
|
||||||
|
import traceback
|
||||||
|
import sys
|
||||||
|
|
||||||
# OC Bot - Coded by Ball Studios 🎨
|
# OC Bot - Coded by Ball Studios 🎨
|
||||||
# A Discord bot for managing and roleplaying as original characters
|
# A Discord bot for managing and roleplaying as original characters
|
||||||
@@ -11,10 +13,34 @@ import logging
|
|||||||
# Load environment variables
|
# Load environment variables
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
# Setup logging
|
# Setup logging with more verbosity
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Log startup info
|
||||||
|
logger.info('=' * 60)
|
||||||
|
logger.info('Starting OC Bot...')
|
||||||
|
logger.info(f'Python: {sys.version}')
|
||||||
|
logger.info(f'discord.py: {discord.__version__}')
|
||||||
|
|
||||||
|
# Verify required environment variables
|
||||||
|
required_env_vars = ['DISCORD_TOKEN', 'DB_HOST', 'DB_USER', 'DB_PASSWORD', 'DB_NAME']
|
||||||
|
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
||||||
|
if missing_vars:
|
||||||
|
logger.error(f'❌ Missing required environment variables: {", ".join(missing_vars)}')
|
||||||
|
for var in missing_vars:
|
||||||
|
logger.error(f' - {var}')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
logger.info('✓ All required environment variables found')
|
||||||
|
logger.info(f'Database Host: {os.getenv("DB_HOST")}')
|
||||||
|
logger.info(f'Database Name: {os.getenv("DB_NAME")}')
|
||||||
|
logger.info(f'Database User: {os.getenv("DB_USER")}')
|
||||||
|
logger.info('=' * 60)
|
||||||
|
|
||||||
# Bot setup
|
# Bot setup
|
||||||
intents = discord.Intents.default()
|
intents = discord.Intents.default()
|
||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
@@ -25,26 +51,53 @@ bot = commands.Bot(command_prefix=',', intents=intents)
|
|||||||
# Database instance
|
# Database instance
|
||||||
db = None
|
db = None
|
||||||
|
|
||||||
|
@bot.command(name='divide')
|
||||||
|
async def divide_command(ctx):
|
||||||
|
"""Sends a Discord GIF link when you type ,divide"""
|
||||||
|
gif_url = "https://cdn.discordapp.com/attachments/1478216070660755569/1516884782188069057/togif.gif?ex=6a34448a&is=6a32f30a&hm=5f505a453c242673c212999ed8f2b666bf455678b3762312fa3eda31b761fc73&"
|
||||||
|
await ctx.send(gif_url)
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
global db
|
global db
|
||||||
logger.info(f'{bot.user} has connected to Discord!')
|
logger.info(f'✓ Discord connection established: {bot.user}')
|
||||||
print('started')
|
|
||||||
if db is None:
|
if db is None:
|
||||||
|
logger.info('Initializing database connection...')
|
||||||
db = Database()
|
db = Database()
|
||||||
await db.connect()
|
try:
|
||||||
|
await db.connect()
|
||||||
|
bot.db = db # Make database accessible to cogs
|
||||||
|
logger.info('✓✓✓ DATABASE CONNECTION SUCCESSFUL ✓✓✓')
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(f'❌❌❌ FAILED TO CONNECT TO DATABASE ❌❌❌')
|
||||||
|
logger.critical(f'Error: {type(e).__name__}: {str(e)}')
|
||||||
|
logger.critical(f'Full traceback:\n{traceback.format_exc()}')
|
||||||
|
logger.critical('Bot cannot function without database. Exiting...')
|
||||||
|
await bot.close()
|
||||||
|
return
|
||||||
|
|
||||||
await load_cogs()
|
await load_cogs()
|
||||||
|
|
||||||
async def load_cogs():
|
async def load_cogs():
|
||||||
"""Load all command cogs"""
|
"""Load all command cogs"""
|
||||||
cogs_dir = 'cogs'
|
cogs_dir = 'cogs'
|
||||||
for filename in os.listdir(cogs_dir):
|
if not os.path.exists(cogs_dir):
|
||||||
if filename.endswith('.py') and not filename.startswith('_'):
|
logger.warning(f'Cogs directory not found: {cogs_dir}')
|
||||||
try:
|
return
|
||||||
await bot.load_extension(f'cogs.{filename[:-3]}')
|
|
||||||
logger.info(f'Loaded cog: {filename}')
|
cog_files = [f for f in os.listdir(cogs_dir) if f.endswith('.py') and not f.startswith('_')]
|
||||||
except Exception as e:
|
logger.info(f'Found {len(cog_files)} cog(s) to load')
|
||||||
logger.error(f'Failed to load cog {filename}: {e}')
|
|
||||||
|
for filename in cog_files:
|
||||||
|
try:
|
||||||
|
cog_name = filename[:-3]
|
||||||
|
await bot.load_extension(f'cogs.{cog_name}')
|
||||||
|
logger.info(f'✓ Loaded cog: {cog_name}')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'❌ Failed to load cog {filename}:')
|
||||||
|
logger.error(f' {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f' Traceback:\n{traceback.format_exc()}')
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_message(message):
|
async def on_message(message):
|
||||||
@@ -61,23 +114,34 @@ async def on_message(message):
|
|||||||
msg_content = parts[1] if len(parts) > 1 else ''
|
msg_content = parts[1] if len(parts) > 1 else ''
|
||||||
|
|
||||||
# Check if this is a registered command
|
# Check if this is a registered command
|
||||||
if trigger in ['oc', 'help', 'ping', 'stats', 'invite', 'support', 'about', 'reload', 'debug', 'admin', 'config']:
|
# ----- ADDED 'divide' TO THE LIST -----
|
||||||
|
if trigger in ['oc', 'help', 'ping', 'stats', 'invite', 'support', 'about', 'reload', 'debug', 'admin', 'config', 'divide']:
|
||||||
await bot.process_commands(message)
|
await bot.process_commands(message)
|
||||||
return
|
return
|
||||||
|
# -------------------------------------
|
||||||
|
|
||||||
# Try to find a character with this trigger
|
# Try to find a character with this trigger
|
||||||
if db:
|
if db is None:
|
||||||
|
logger.warning('Database not connected - cannot process character trigger')
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
character = await db.get_character_by_trigger(message.author.id, trigger)
|
character = await db.get_character_by_trigger(message.author.id, trigger)
|
||||||
if character:
|
if character:
|
||||||
|
logger.info(f'Triggered character: {character.get("name", "Unknown")} (trigger: {trigger}, user: {message.author.id})')
|
||||||
# Delete original message
|
# Delete original message
|
||||||
try:
|
try:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
logger.warning(f'Could not delete message: {e}')
|
||||||
|
|
||||||
# Create webhook and send message
|
# Create webhook and send message
|
||||||
await send_character_message(message.channel, character, msg_content)
|
await send_character_message(message.channel, character, msg_content)
|
||||||
return
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Error processing character trigger "{trigger}":')
|
||||||
|
logger.error(f' {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f' Traceback:\n{traceback.format_exc()}')
|
||||||
|
|
||||||
# Process other commands
|
# Process other commands
|
||||||
await bot.process_commands(message)
|
await bot.process_commands(message)
|
||||||
@@ -85,6 +149,8 @@ async def on_message(message):
|
|||||||
async def send_character_message(channel, character, content):
|
async def send_character_message(channel, character, content):
|
||||||
"""Send a message through webhook as a character"""
|
"""Send a message through webhook as a character"""
|
||||||
try:
|
try:
|
||||||
|
logger.info(f'Sending message as {character.get("name")} to {channel}')
|
||||||
|
|
||||||
# Get existing webhooks
|
# Get existing webhooks
|
||||||
webhooks = await channel.webhooks()
|
webhooks = await channel.webhooks()
|
||||||
webhook = None
|
webhook = None
|
||||||
@@ -97,25 +163,58 @@ async def send_character_message(channel, character, content):
|
|||||||
|
|
||||||
# Create webhook if doesn't exist
|
# Create webhook if doesn't exist
|
||||||
if not webhook:
|
if not webhook:
|
||||||
|
logger.info(f'Creating webhook for channel {channel}')
|
||||||
webhook = await channel.create_webhook(name='OC-Bot')
|
webhook = await channel.create_webhook(name='OC-Bot')
|
||||||
|
|
||||||
# Send message through webhook
|
# Get avatar URL safely (fallback to None if not present)
|
||||||
await webhook.send(
|
avatar_url = character.get('avatar_url') # can be None
|
||||||
|
|
||||||
|
# Send message through webhook, wait=True ensures we get a Message object
|
||||||
|
msg = await webhook.send(
|
||||||
content=content,
|
content=content,
|
||||||
username=character['name'],
|
username=character['name'],
|
||||||
avatar_url=character['avatar_url']
|
avatar_url=avatar_url,
|
||||||
|
wait=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Defensive check: webhook.send() might return None in some edge cases
|
||||||
|
if msg is None:
|
||||||
|
logger.error(f'❌ webhook.send() returned None for character {character["name"]}')
|
||||||
|
logger.error(f' content: {content[:50]}, avatar_url: {avatar_url}')
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(f'✓ Message sent successfully: {msg.id}')
|
||||||
|
except discord.HTTPException as e:
|
||||||
|
# Specific HTTP error from Discord API
|
||||||
|
logger.error(f'❌ HTTP error sending webhook message: {e.status} {e.text}')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'Failed to send character message: {e}')
|
logger.error(f'❌ Failed to send character message:')
|
||||||
|
logger.error(f' Character: {character.get("name", "Unknown")}')
|
||||||
|
logger.error(f' Channel: {channel}')
|
||||||
|
logger.error(f' {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f' Traceback:\n{traceback.format_exc()}')
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_command_error(ctx, error):
|
async def on_command_error(ctx, error):
|
||||||
"""Error handling for commands"""
|
"""Error handling for commands"""
|
||||||
logger.error(f'Command error: {error}')
|
logger.error(f'❌ Command error in {ctx.command}:')
|
||||||
|
logger.error(f' {type(error).__name__}: {str(error)}')
|
||||||
|
logger.error(f' Traceback:\n{traceback.format_exc()}')
|
||||||
|
|
||||||
# Run bot
|
# Run bot
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
TOKEN = os.getenv('DISCORD_TOKEN')
|
TOKEN = os.getenv('DISCORD_TOKEN')
|
||||||
if not TOKEN:
|
if not TOKEN:
|
||||||
raise ValueError('DISCORD_TOKEN not found in .env file')
|
logger.critical('❌ DISCORD_TOKEN not found in .env file')
|
||||||
bot.run(TOKEN)
|
sys.exit(1)
|
||||||
|
|
||||||
|
logger.info('Starting bot with Discord token...')
|
||||||
|
try:
|
||||||
|
bot.run(TOKEN)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info('Bot interrupted by user')
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(f'❌ Fatal error running bot:')
|
||||||
|
logger.critical(f' {type(e).__name__}: {str(e)}')
|
||||||
|
logger.critical(f' Traceback:\n{traceback.format_exc()}')
|
||||||
|
sys.exit(1)
|
||||||
156
cogs/help.py
156
cogs/help.py
@@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
@@ -35,25 +36,25 @@ class Help(commands.Cog):
|
|||||||
async def show_main_help(self, ctx):
|
async def show_main_help(self, ctx):
|
||||||
"""Show main help menu"""
|
"""Show main help menu"""
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title='🎭 Original Character Bot',
|
title='OC Bot - Character Management',
|
||||||
description='Manage and roleplay as your original characters!',
|
description='Manage and roleplay as your original characters!',
|
||||||
color=discord.Color.blue()
|
color=discord.Color.blue()
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='📚 What are OCs?',
|
name='What are OCs?',
|
||||||
value='OCs (Original Characters) are fictional characters you create. This bot lets you manage them and send messages as them!',
|
value='OCs (Original Characters) are fictional characters you create. This bot lets you manage them and send messages as them!',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='🚀 Getting Started',
|
name='Getting Started',
|
||||||
value='`,oc create` - Start creating your first character!\n`,oc list` - View all your characters',
|
value='`,oc create` - Start creating your first character!\n`,oc list` - View all your characters',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='📖 Commands',
|
name='Commands',
|
||||||
value='**Character Management:**\n'
|
value='**Character Management:**\n'
|
||||||
'`,oc create` - Create a new character\n'
|
'`,oc create` - Create a new character\n'
|
||||||
'`,oc list` - List your characters\n'
|
'`,oc list` - List your characters\n'
|
||||||
@@ -68,7 +69,7 @@ class Help(commands.Cog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='💡 Quick Example',
|
name='Quick Example',
|
||||||
value='1. `,oc create` and follow the wizard\n'
|
value='1. `,oc create` and follow the wizard\n'
|
||||||
'2. Use `,ba Hello everyone` to send as your character\n'
|
'2. Use `,ba Hello everyone` to send as your character\n'
|
||||||
'3. `,oc list` to see all your characters',
|
'3. `,oc list` to see all your characters',
|
||||||
@@ -76,7 +77,7 @@ class Help(commands.Cog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='📝 Help Topics',
|
name='Help Topics',
|
||||||
value='`,help create` - Character creation guide\n'
|
value='`,help create` - Character creation guide\n'
|
||||||
'`,help triggers` - How triggers work\n'
|
'`,help triggers` - How triggers work\n'
|
||||||
'`,help oc` - OC command overview\n'
|
'`,help oc` - OC command overview\n'
|
||||||
@@ -85,7 +86,7 @@ class Help(commands.Cog):
|
|||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_footer(text='Coded by Ball Studios 🎨')
|
embed.set_footer(text='Coded by Ball Studios')
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
async def help_create(self, ctx):
|
async def help_create(self, ctx):
|
||||||
@@ -110,9 +111,9 @@ class Help(commands.Cog):
|
|||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Step-by-step',
|
name='Step-by-step',
|
||||||
value='1. Run `,oc create`\n'
|
value='1. Run `,oc create`\n'
|
||||||
'2. Enter your character\'s name\n'
|
'2. Enter your character name\n'
|
||||||
'3. Provide an avatar URL (image link)\n'
|
'3. Provide an avatar URL (image link)\n'
|
||||||
'4. Choose a unique trigger (e.g., `ba`, `sa`)\n'
|
'4. Choose a unique trigger (e.g., ba, sa)\n'
|
||||||
'5. Add an optional description\n'
|
'5. Add an optional description\n'
|
||||||
'6. Confirm!',
|
'6. Confirm!',
|
||||||
inline=False
|
inline=False
|
||||||
@@ -120,31 +121,31 @@ class Help(commands.Cog):
|
|||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Avatar URL',
|
name='Avatar URL',
|
||||||
value='This should be a direct link to an image:\n'
|
value='This should be a direct link to an image.\n'
|
||||||
'✅ `https://example.com/avatar.png`\n'
|
'OK: https://example.com/avatar.png\n'
|
||||||
'❌ Discord user profile link\n'
|
'NOT OK: Discord user profile link\n'
|
||||||
'❌ Local file path',
|
'NOT OK: Local file path',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Trigger',
|
name='Trigger',
|
||||||
value='A short code (2-5 chars) that activates your character.\n'
|
value='A short code (2-5 chars) that activates your character.\n'
|
||||||
'You\'ll use it like: `,ba Hello!`\n'
|
'You will use it like: `,ba Hello!`\n'
|
||||||
'Must be unique to you (others can have `,ba` too)',
|
'Must be unique to you (others can have `,ba` too)',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Example',
|
name='Example',
|
||||||
value='* Name: **Barny**\n'
|
value='Name: Barny\n'
|
||||||
'* Avatar: `https://example.com/barny.png`\n'
|
'Avatar: https://example.com/barny.png\n'
|
||||||
'* Trigger: `ba`\n'
|
'Trigger: ba\n'
|
||||||
'* Final command: `,ba Hello everyone!`',
|
'Final command: `,ba Hello everyone!`',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_footer(text='Coded by Ball Studios 🎨')
|
embed.set_footer(text='Coded by Ball Studios')
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
async def help_triggers(self, ctx):
|
async def help_triggers(self, ctx):
|
||||||
@@ -162,19 +163,19 @@ class Help(commands.Cog):
|
|||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='How to use triggers',
|
name='How to use triggers',
|
||||||
value='If your character has trigger `ba`:\n'
|
value='If your character has trigger ba:\n'
|
||||||
'`,ba Hello!` → Message sent as your character\n'
|
'`,ba Hello!` - Message sent as your character\n'
|
||||||
'`,ba I\'m excited!` → Another message as them',
|
'`,ba I am excited!` - Another message as them',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Trigger Rules',
|
name='Trigger Rules',
|
||||||
value='✅ 1-10 characters\n'
|
value='OK: 1-10 characters\n'
|
||||||
'✅ Lowercase letters (a-z)\n'
|
'OK: Lowercase letters (a-z)\n'
|
||||||
'✅ Numbers (0-9)\n'
|
'OK: Numbers (0-9)\n'
|
||||||
'❌ Cannot be reserved words (help, oc, ping, etc.)\n'
|
'NOT OK: Reserved words (help, oc, ping, etc.)\n'
|
||||||
'❌ Must be unique to YOU (no duplicates per user)',
|
'NOT OK: Must be unique to YOU (no duplicates per user)',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -195,6 +196,7 @@ class Help(commands.Cog):
|
|||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
embed.set_footer(text='Coded by Ball Studios')
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
async def help_oc(self, ctx):
|
async def help_oc(self, ctx):
|
||||||
@@ -236,10 +238,11 @@ class Help(commands.Cog):
|
|||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='`,oc preview <name>`',
|
name='`,oc preview <name>`',
|
||||||
value='See how a character\'s message will look',
|
value='See how a character message will look',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
embed.set_footer(text='Coded by Ball Studios')
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
async def help_edit(self, ctx):
|
async def help_edit(self, ctx):
|
||||||
@@ -257,10 +260,10 @@ class Help(commands.Cog):
|
|||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='What can you edit?',
|
name='What can you edit?',
|
||||||
value='• **Name** - Your character\'s name\n'
|
value='- Name: Your character name\n'
|
||||||
'• **Avatar** - Their profile picture URL\n'
|
'- Avatar: Their profile picture URL\n'
|
||||||
'• **Trigger** - Their command code\n'
|
'- Trigger: Their command code\n'
|
||||||
'• **Description** - Background info',
|
'- Description: Background info',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -276,13 +279,13 @@ class Help(commands.Cog):
|
|||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Trigger Change Tips',
|
name='Trigger Change Tips',
|
||||||
value='When changing a trigger:\n'
|
value='When changing a trigger:\n'
|
||||||
'• Old trigger becomes unusable\n'
|
'- Old trigger becomes unusable\n'
|
||||||
'• New trigger must be unique\n'
|
'- New trigger must be unique\n'
|
||||||
'• Use new trigger immediately\n'
|
'- You will need to use new command immediately',
|
||||||
'• E.g., if `,ba` → `,bn`, use `,bn` now',
|
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
embed.set_footer(text='Coded by Ball Studios')
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
async def help_delete(self, ctx):
|
async def help_delete(self, ctx):
|
||||||
@@ -299,37 +302,28 @@ class Help(commands.Cog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='What happens?',
|
name='Warning',
|
||||||
value='1. Bot asks for confirmation\n'
|
value='This action cannot be undone!',
|
||||||
'2. Type `YES` within 30 seconds\n'
|
|
||||||
'3. Character is permanently deleted\n'
|
|
||||||
'4. Trigger becomes available again',
|
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='⚠️ Warning',
|
name='How to delete',
|
||||||
value='Deletion is **permanent**! You cannot undo this.\n'
|
value='1. `,oc delete Barny`\n'
|
||||||
'Create a backup/description if needed.',
|
'2. Bot asks for confirmation\n'
|
||||||
inline=False
|
'3. Type YES (within 30 seconds)\n'
|
||||||
)
|
'4. Character is permanently deleted',
|
||||||
|
|
||||||
embed.add_field(
|
|
||||||
name='Example',
|
|
||||||
value='User: `,oc delete Barny`\n'
|
|
||||||
'Bot: Are you sure? Type YES to confirm.\n'
|
|
||||||
'User: `YES`\n'
|
|
||||||
'Bot: Character Barny deleted.',
|
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
embed.set_footer(text='Coded by Ball Studios')
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
async def help_list(self, ctx):
|
async def help_list(self, ctx):
|
||||||
"""Help for listing characters"""
|
"""Help for list command"""
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title='Listing Your Characters',
|
title='List Your Characters',
|
||||||
color=discord.Color.blue()
|
color=discord.Color.blurple()
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
@@ -339,30 +333,27 @@ class Help(commands.Cog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='What does it show?',
|
name='What it shows',
|
||||||
value='A list of all your characters with their triggers',
|
value='All your characters with their triggers',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Example Output',
|
name='Example output',
|
||||||
value='**Your Characters**\n'
|
value='Character 1: Barny (trigger: ba)\n'
|
||||||
'Barny\n'
|
'Character 2: Dragon (trigger: dr)\n'
|
||||||
'Trigger: `,ba`\n\n'
|
'Character 3: Shy (trigger: sh)',
|
||||||
'Sarah\n'
|
|
||||||
'Trigger: `,sa`\n\n'
|
|
||||||
'Knight\n'
|
|
||||||
'Trigger: `,kn`',
|
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
embed.set_footer(text='Coded by Ball Studios')
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
async def help_info(self, ctx):
|
async def help_info(self, ctx):
|
||||||
"""Help for info command"""
|
"""Help for info command"""
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title='Character Information',
|
title='Character Info',
|
||||||
color=discord.Color.blurple()
|
color=discord.Color.dark_blue()
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
@@ -372,30 +363,26 @@ class Help(commands.Cog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='What does it show?',
|
name='What it shows',
|
||||||
value='Detailed information about a character:\n'
|
value='Name, trigger, avatar URL, and description',
|
||||||
'• Name\n'
|
|
||||||
'• Trigger code\n'
|
|
||||||
'• Avatar image\n'
|
|
||||||
'• Description\n'
|
|
||||||
'• Creation date\n'
|
|
||||||
'• Last time used',
|
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Example',
|
name='Example',
|
||||||
value='`,oc info Barny`',
|
value='`,oc info Barny`\n\n'
|
||||||
|
'Shows all details about your Barny character',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
embed.set_footer(text='Coded by Ball Studios')
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
async def help_preview(self, ctx):
|
async def help_preview(self, ctx):
|
||||||
"""Help for preview command"""
|
"""Help for preview command"""
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title='Character Preview',
|
title='Preview Character',
|
||||||
color=discord.Color.green()
|
color=discord.Color.teal()
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
@@ -405,22 +392,19 @@ class Help(commands.Cog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='What does it do?',
|
name='What it does',
|
||||||
value='Shows you exactly how your character will appear when sending messages.\n'
|
value='Shows how a character message will appear',
|
||||||
'Useful to check:\n'
|
|
||||||
'• Avatar displays correctly\n'
|
|
||||||
'• Name looks good\n'
|
|
||||||
'• Everything works as expected',
|
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Example',
|
name='Example',
|
||||||
value='`,oc preview Barny`\n\n'
|
value='`,oc preview Barny`\n\n'
|
||||||
'Bot sends a sample message with Barny\'s avatar and name',
|
'Bot sends a sample message with Barny avatar and name',
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
embed.set_footer(text='Coded by Ball Studios')
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
|
|||||||
324
database.py
324
database.py
@@ -1,9 +1,9 @@
|
|||||||
import mysql.connector
|
import aiomysql
|
||||||
from mysql.connector import Error
|
|
||||||
import os
|
import os
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import traceback
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -11,63 +11,77 @@ logger = logging.getLogger(__name__)
|
|||||||
class Database:
|
class Database:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.connection = None
|
self.connection = None
|
||||||
self.cursor = None
|
|
||||||
self.trigger_cache = {} # Cache for (user_id, trigger) -> character_id lookups
|
self.trigger_cache = {} # Cache for (user_id, trigger) -> character_id lookups
|
||||||
|
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
"""Connect to MariaDB"""
|
"""Connect to MariaDB"""
|
||||||
try:
|
try:
|
||||||
self.connection = mysql.connector.connect(
|
db_host = os.getenv('DB_HOST', 'localhost')
|
||||||
host=os.getenv('DB_HOST', 'localhost'),
|
db_user = os.getenv('DB_USER', 'root')
|
||||||
user=os.getenv('DB_USER', 'root'),
|
db_password = os.getenv('DB_PASSWORD', '')
|
||||||
password=os.getenv('DB_PASSWORD', ''),
|
db_name = os.getenv('DB_NAME', 'rp_bot')
|
||||||
database=os.getenv('DB_NAME', 'rp_bot')
|
|
||||||
|
logger.info(f'Attempting to connect to MariaDB at {db_host}:{db_name} as {db_user}')
|
||||||
|
|
||||||
|
self.connection = await aiomysql.create_pool(
|
||||||
|
host=db_host,
|
||||||
|
user=db_user,
|
||||||
|
password=db_password,
|
||||||
|
db=db_name,
|
||||||
|
minsize=1,
|
||||||
|
maxsize=10,
|
||||||
|
autocommit=True
|
||||||
)
|
)
|
||||||
self.cursor = self.connection.cursor(dictionary=True)
|
logger.info('✓ Successfully connected to MariaDB')
|
||||||
logger.info('Connected to MariaDB')
|
|
||||||
await self.initialize_tables()
|
await self.initialize_tables()
|
||||||
except Error as e:
|
except Exception as e:
|
||||||
logger.error(f'Database connection error: {e}')
|
error_msg = f'Database connection failed: {type(e).__name__}: {str(e)}'
|
||||||
|
logger.error(error_msg)
|
||||||
|
logger.error(f'Full traceback:\n{traceback.format_exc()}')
|
||||||
|
logger.error(f'Connection details - Host: {os.getenv("DB_HOST")}, User: {os.getenv("DB_USER")}, DB: {os.getenv("DB_NAME")}')
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def initialize_tables(self):
|
async def initialize_tables(self):
|
||||||
"""Create necessary tables if they don't exist"""
|
"""Create necessary tables if they don't exist"""
|
||||||
try:
|
try:
|
||||||
# Users table
|
async with self.connection.acquire() as conn:
|
||||||
self.cursor.execute('''
|
async with conn.cursor() as cursor:
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
# Users table
|
||||||
user_id BIGINT PRIMARY KEY,
|
await cursor.execute('''
|
||||||
username VARCHAR(255),
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
user_id BIGINT PRIMARY KEY,
|
||||||
)
|
username VARCHAR(255),
|
||||||
''')
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
# Characters table
|
# Characters table
|
||||||
self.cursor.execute('''
|
await cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS `characters` (
|
CREATE TABLE IF NOT EXISTS `characters` (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
user_id BIGINT NOT NULL,
|
user_id BIGINT NOT NULL,
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
`trigger` VARCHAR(100) NOT NULL,
|
`trigger` VARCHAR(100) NOT NULL,
|
||||||
avatar_url TEXT,
|
avatar_url TEXT,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
last_used TIMESTAMP NULL,
|
last_used TIMESTAMP NULL,
|
||||||
FOREIGN KEY (user_id) REFERENCES users(user_id),
|
FOREIGN KEY (user_id) REFERENCES users(user_id),
|
||||||
UNIQUE KEY unique_user_trigger (user_id, `trigger`)
|
UNIQUE KEY unique_user_trigger (user_id, `trigger`)
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
# Index for fast trigger lookups
|
# Index for fast trigger lookups
|
||||||
self.cursor.execute('''
|
await cursor.execute('''
|
||||||
CREATE INDEX IF NOT EXISTS idx_user_trigger
|
CREATE INDEX IF NOT EXISTS idx_user_trigger
|
||||||
ON `characters`(user_id, `trigger`)
|
ON `characters`(user_id, `trigger`)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
self.connection.commit()
|
logger.info('✓ Database tables initialized successfully')
|
||||||
logger.info('Database tables initialized')
|
except Exception as e:
|
||||||
except Error as e:
|
logger.error(f'Table initialization error: {type(e).__name__}: {str(e)}')
|
||||||
logger.error(f'Table initialization error: {e}')
|
logger.error(f'Traceback:\n{traceback.format_exc()}')
|
||||||
|
raise
|
||||||
|
|
||||||
async def get_character_by_trigger(self, user_id, trigger):
|
async def get_character_by_trigger(self, user_id, trigger):
|
||||||
"""Get character by user_id and trigger (O(1) lookup with caching)"""
|
"""Get character by user_id and trigger (O(1) lookup with caching)"""
|
||||||
@@ -79,68 +93,80 @@ class Database:
|
|||||||
return await self.get_character_by_id(char_id)
|
return await self.get_character_by_id(char_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.cursor.execute(
|
async with self.connection.acquire() as conn:
|
||||||
'SELECT * FROM `characters` WHERE user_id = %s AND `trigger` = %s',
|
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||||
(user_id, trigger)
|
await cursor.execute(
|
||||||
)
|
'SELECT * FROM `characters` WHERE user_id = %s AND `trigger` = %s',
|
||||||
result = self.cursor.fetchone()
|
(user_id, trigger)
|
||||||
|
)
|
||||||
|
result = await cursor.fetchone()
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
# Cache the result
|
# Cache the result
|
||||||
self.trigger_cache[cache_key] = result['id']
|
self.trigger_cache[cache_key] = result['id']
|
||||||
|
|
||||||
return result
|
return result
|
||||||
except Error as e:
|
except Exception as e:
|
||||||
logger.error(f'Get character error: {e}')
|
logger.error(f'Get character error: {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f'Traceback:\n{traceback.format_exc()}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_character_by_id(self, char_id):
|
async def get_character_by_id(self, char_id):
|
||||||
"""Get character by ID"""
|
"""Get character by ID"""
|
||||||
try:
|
try:
|
||||||
self.cursor.execute('SELECT * FROM `characters` WHERE id = %s', (char_id,))
|
async with self.connection.acquire() as conn:
|
||||||
return self.cursor.fetchone()
|
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||||
except Error as e:
|
await cursor.execute('SELECT * FROM `characters` WHERE id = %s', (char_id,))
|
||||||
logger.error(f'Get character by ID error: {e}')
|
return await cursor.fetchone()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Get character by ID error: {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f'Traceback:\n{traceback.format_exc()}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def create_character(self, user_id, name, trigger, avatar_url, description=''):
|
async def create_character(self, user_id, name, trigger, avatar_url, description=''):
|
||||||
"""Create a new character"""
|
"""Create a new character"""
|
||||||
try:
|
try:
|
||||||
# Ensure user exists
|
async with self.connection.acquire() as conn:
|
||||||
self.cursor.execute(
|
async with conn.cursor() as cursor:
|
||||||
'INSERT IGNORE INTO users (user_id) VALUES (%s)',
|
# Ensure user exists
|
||||||
(user_id,)
|
await cursor.execute(
|
||||||
)
|
'INSERT IGNORE INTO users (user_id) VALUES (%s)',
|
||||||
|
(user_id,)
|
||||||
|
)
|
||||||
|
|
||||||
# Insert character
|
# Insert character
|
||||||
self.cursor.execute(
|
await cursor.execute(
|
||||||
'''INSERT INTO `characters`
|
'''INSERT INTO `characters`
|
||||||
(user_id, name, `trigger`, avatar_url, description)
|
(user_id, name, `trigger`, avatar_url, description)
|
||||||
VALUES (%s, %s, %s, %s, %s)''',
|
VALUES (%s, %s, %s, %s, %s)''',
|
||||||
(user_id, name, trigger, avatar_url, description)
|
(user_id, name, trigger, avatar_url, description)
|
||||||
)
|
)
|
||||||
self.connection.commit()
|
|
||||||
|
|
||||||
# Clear cache for this user's trigger
|
# Clear cache for this user's trigger
|
||||||
cache_key = (user_id, trigger)
|
cache_key = (user_id, trigger)
|
||||||
if cache_key in self.trigger_cache:
|
if cache_key in self.trigger_cache:
|
||||||
del self.trigger_cache[cache_key]
|
del self.trigger_cache[cache_key]
|
||||||
|
|
||||||
return self.cursor.lastrowid
|
logger.info(f'Created character "{name}" with trigger "{trigger}" for user {user_id}')
|
||||||
except Error as e:
|
return cursor.lastrowid
|
||||||
logger.error(f'Create character error: {e}')
|
except Exception as e:
|
||||||
|
logger.error(f'Create character error: {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f'Traceback:\n{traceback.format_exc()}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_user_characters(self, user_id):
|
async def get_user_characters(self, user_id):
|
||||||
"""Get all characters for a user"""
|
"""Get all characters for a user"""
|
||||||
try:
|
try:
|
||||||
self.cursor.execute(
|
async with self.connection.acquire() as conn:
|
||||||
'SELECT * FROM `characters` WHERE user_id = %s ORDER BY created_at DESC',
|
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||||
(user_id,)
|
await cursor.execute(
|
||||||
)
|
'SELECT * FROM `characters` WHERE user_id = %s ORDER BY created_at DESC',
|
||||||
return self.cursor.fetchall()
|
(user_id,)
|
||||||
except Error as e:
|
)
|
||||||
logger.error(f'Get user characters error: {e}')
|
return await cursor.fetchall()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Get user characters error: {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f'Traceback:\n{traceback.format_exc()}')
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def update_character(self, char_id, **kwargs):
|
async def update_character(self, char_id, **kwargs):
|
||||||
@@ -152,86 +178,100 @@ class Database:
|
|||||||
if not updates:
|
if not updates:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Get old trigger for cache clearing
|
async with self.connection.acquire() as conn:
|
||||||
self.cursor.execute('SELECT user_id, `trigger` FROM `characters` WHERE id = %s', (char_id,))
|
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||||
old_char = self.cursor.fetchone()
|
# Get old trigger for cache clearing
|
||||||
|
await cursor.execute('SELECT user_id, `trigger` FROM `characters` WHERE id = %s', (char_id,))
|
||||||
|
old_char = await cursor.fetchone()
|
||||||
|
|
||||||
set_clause = ', '.join([f'`{k}` = %s' if k == 'trigger' else f'{k} = %s' for k in updates.keys()])
|
set_clause = ', '.join([f'`{k}` = %s' if k == 'trigger' else f'{k} = %s' for k in updates.keys()])
|
||||||
values = list(updates.values()) + [char_id]
|
values = list(updates.values()) + [char_id]
|
||||||
|
|
||||||
self.cursor.execute(
|
await cursor.execute(
|
||||||
f'UPDATE `characters` SET {set_clause} WHERE id = %s',
|
f'UPDATE `characters` SET {set_clause} WHERE id = %s',
|
||||||
values
|
values
|
||||||
)
|
)
|
||||||
self.connection.commit()
|
|
||||||
|
|
||||||
# Clear cache
|
# Clear cache
|
||||||
if old_char:
|
if old_char:
|
||||||
old_cache_key = (old_char['user_id'], old_char['trigger'])
|
old_cache_key = (old_char['user_id'], old_char['trigger'])
|
||||||
if old_cache_key in self.trigger_cache:
|
if old_cache_key in self.trigger_cache:
|
||||||
del self.trigger_cache[old_cache_key]
|
del self.trigger_cache[old_cache_key]
|
||||||
|
|
||||||
# If trigger changed, clear new trigger cache too
|
# If trigger changed, clear new trigger cache too
|
||||||
if 'trigger' in updates:
|
if 'trigger' in updates:
|
||||||
new_cache_key = (old_char['user_id'], updates['trigger'])
|
new_cache_key = (old_char['user_id'], updates['trigger'])
|
||||||
if new_cache_key in self.trigger_cache:
|
if new_cache_key in self.trigger_cache:
|
||||||
del self.trigger_cache[new_cache_key]
|
del self.trigger_cache[new_cache_key]
|
||||||
|
|
||||||
return True
|
logger.info(f'Updated character {char_id}: {updates}')
|
||||||
except Error as e:
|
return True
|
||||||
logger.error(f'Update character error: {e}')
|
except Exception as e:
|
||||||
|
logger.error(f'Update character error: {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f'Traceback:\n{traceback.format_exc()}')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def delete_character(self, char_id):
|
async def delete_character(self, char_id):
|
||||||
"""Delete a character"""
|
"""Delete a character"""
|
||||||
try:
|
try:
|
||||||
# Get character info for cache clearing
|
async with self.connection.acquire() as conn:
|
||||||
self.cursor.execute('SELECT user_id, `trigger` FROM `characters` WHERE id = %s', (char_id,))
|
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||||
char = self.cursor.fetchone()
|
# Get character info for cache clearing
|
||||||
|
await cursor.execute('SELECT user_id, `trigger` FROM `characters` WHERE id = %s', (char_id,))
|
||||||
|
char = await cursor.fetchone()
|
||||||
|
|
||||||
self.cursor.execute('DELETE FROM `characters` WHERE id = %s', (char_id,))
|
await cursor.execute('DELETE FROM `characters` WHERE id = %s', (char_id,))
|
||||||
self.connection.commit()
|
|
||||||
|
|
||||||
# Clear cache
|
# Clear cache
|
||||||
if char:
|
if char:
|
||||||
cache_key = (char['user_id'], char['trigger'])
|
cache_key = (char['user_id'], char['trigger'])
|
||||||
if cache_key in self.trigger_cache:
|
if cache_key in self.trigger_cache:
|
||||||
del self.trigger_cache[cache_key]
|
del self.trigger_cache[cache_key]
|
||||||
|
|
||||||
return True
|
logger.info(f'Deleted character {char_id}')
|
||||||
except Error as e:
|
return True
|
||||||
logger.error(f'Delete character error: {e}')
|
except Exception as e:
|
||||||
|
logger.error(f'Delete character error: {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f'Traceback:\n{traceback.format_exc()}')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def update_last_used(self, char_id):
|
async def update_last_used(self, char_id):
|
||||||
"""Update last_used timestamp for a character"""
|
"""Update last_used timestamp for a character"""
|
||||||
try:
|
try:
|
||||||
self.cursor.execute(
|
async with self.connection.acquire() as conn:
|
||||||
'UPDATE `characters` SET last_used = NOW() WHERE id = %s',
|
async with conn.cursor() as cursor:
|
||||||
(char_id,)
|
await cursor.execute(
|
||||||
)
|
'UPDATE `characters` SET last_used = NOW() WHERE id = %s',
|
||||||
self.connection.commit()
|
(char_id,)
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
except Error as e:
|
except Exception as e:
|
||||||
logger.error(f'Update last used error: {e}')
|
logger.error(f'Update last used error: {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f'Traceback:\n{traceback.format_exc()}')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def is_trigger_available(self, user_id, trigger):
|
async def is_trigger_available(self, user_id, trigger):
|
||||||
"""Check if trigger is available for a user"""
|
"""Check if trigger is available for a user"""
|
||||||
try:
|
try:
|
||||||
self.cursor.execute(
|
async with self.connection.acquire() as conn:
|
||||||
'SELECT id FROM `characters` WHERE user_id = %s AND `trigger` = %s',
|
async with conn.cursor() as cursor:
|
||||||
(user_id, trigger)
|
await cursor.execute(
|
||||||
)
|
'SELECT id FROM `characters` WHERE user_id = %s AND `trigger` = %s',
|
||||||
return self.cursor.fetchone() is None
|
(user_id, trigger)
|
||||||
except Error as e:
|
)
|
||||||
logger.error(f'Check trigger availability error: {e}')
|
return await cursor.fetchone() is None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Check trigger availability error: {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f'Traceback:\n{traceback.format_exc()}')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def close(self):
|
async def close(self):
|
||||||
"""Close database connection"""
|
"""Close database connection"""
|
||||||
if self.cursor:
|
try:
|
||||||
self.cursor.close()
|
if self.connection:
|
||||||
if self.connection:
|
self.connection.close()
|
||||||
self.connection.close()
|
await self.connection.wait_closed()
|
||||||
logger.info('Database connection closed')
|
logger.info('✓ Database connection closed')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Error closing database: {type(e).__name__}: {str(e)}')
|
||||||
|
logger.error(f'Traceback:\n{traceback.format_exc()}')
|
||||||
|
|||||||
Reference in New Issue
Block a user