383 lines
14 KiB
Python
383 lines
14 KiB
Python
import discord
|
|
from discord.ext import commands
|
|
import logging
|
|
import asyncio
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
RESERVED_TRIGGERS = ['help', 'oc', 'ping', 'stats', 'invite', 'support', 'about', 'reload', 'debug', 'admin', 'config']
|
|
|
|
class OC(commands.Cog):
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
|
|
@commands.group(name='oc', invoke_without_command=True)
|
|
async def oc_group(self, ctx):
|
|
"""Original Character management commands"""
|
|
if ctx.invoked_subcommand is None:
|
|
embed = discord.Embed(
|
|
title='🎭 OC Commands',
|
|
description='Use `,oc <command>` for character management',
|
|
color=discord.Color.purple()
|
|
)
|
|
embed.add_field(name='`,oc create`', value='Create a new character', inline=False)
|
|
embed.add_field(name='`,oc list`', value='List your characters', inline=False)
|
|
embed.add_field(name='`,oc info <name>`', value='View character details', inline=False)
|
|
embed.add_field(name='`,oc edit <name>`', value='Edit a character', inline=False)
|
|
embed.add_field(name='`,oc delete <name>`', value='Delete a character', inline=False)
|
|
embed.add_field(name='`,oc preview <name>`', value='Preview character message', inline=False)
|
|
await ctx.send(embed=embed)
|
|
|
|
async def prompt_user(self, ctx, prompt_text, timeout=120):
|
|
"""Helper to get user input with timeout"""
|
|
await ctx.send(prompt_text)
|
|
|
|
try:
|
|
msg = await self.bot.wait_for(
|
|
'message',
|
|
check=lambda m: m.author == ctx.author and m.channel == ctx.channel,
|
|
timeout=timeout
|
|
)
|
|
return msg.content
|
|
except asyncio.TimeoutError:
|
|
await ctx.send('❌ Cancelled (timeout).')
|
|
return None
|
|
|
|
@oc_group.command(name='create')
|
|
async def create(self, ctx):
|
|
"""Interactive character creation"""
|
|
db = self.bot.db if hasattr(self.bot, 'db') else None
|
|
if not db:
|
|
await ctx.send('❌ Database connection error.')
|
|
return
|
|
|
|
# Get character name
|
|
name = await self.prompt_user(ctx, 'What is your character name?')
|
|
if not name:
|
|
return
|
|
|
|
# Get avatar URL
|
|
avatar_url = await self.prompt_user(ctx, 'What avatar URL would you like to use?')
|
|
if not avatar_url:
|
|
return
|
|
|
|
# Get trigger
|
|
while True:
|
|
trigger = await self.prompt_user(ctx, 'What trigger would you like? (Example: `ba`)')
|
|
if not trigger:
|
|
return
|
|
|
|
# Check if reserved
|
|
if trigger.lower() in RESERVED_TRIGGERS:
|
|
await ctx.send(f'❌ `{trigger}` is reserved. Please choose another.')
|
|
continue
|
|
|
|
# Check if available
|
|
if await db.is_trigger_available(ctx.author.id, trigger):
|
|
break
|
|
else:
|
|
await ctx.send(f'❌ Trigger `,{trigger}` is already in use. Please choose another.')
|
|
|
|
# Get description (optional)
|
|
await ctx.send('Description? (optional - type `skip` to skip)')
|
|
|
|
try:
|
|
msg = await self.bot.wait_for(
|
|
'message',
|
|
check=lambda m: m.author == ctx.author and m.channel == ctx.channel,
|
|
timeout=120
|
|
)
|
|
description = '' if msg.content.lower() == 'skip' else msg.content
|
|
except asyncio.TimeoutError:
|
|
await ctx.send('❌ Character creation cancelled.')
|
|
return
|
|
|
|
# Create character
|
|
char_id = await db.create_character(
|
|
ctx.author.id,
|
|
name,
|
|
trigger,
|
|
avatar_url,
|
|
description
|
|
)
|
|
|
|
if char_id:
|
|
embed = discord.Embed(
|
|
title='✅ Character Created!',
|
|
color=discord.Color.green()
|
|
)
|
|
embed.add_field(name='Name', value=name, inline=False)
|
|
embed.add_field(name='Trigger', value=f'`,{trigger}`', inline=False)
|
|
embed.add_field(name='Avatar', value=f'[Link]({avatar_url})', inline=False)
|
|
if description:
|
|
embed.add_field(name='Description', value=description, inline=False)
|
|
embed.set_footer(text='Use your trigger to send messages as this character!')
|
|
await ctx.send(embed=embed)
|
|
else:
|
|
await ctx.send('❌ Failed to create character.')
|
|
|
|
@oc_group.command(name='list')
|
|
async def list_characters(self, ctx):
|
|
"""List all user's characters"""
|
|
db = self.bot.db if hasattr(self.bot, 'db') else None
|
|
if not db:
|
|
await ctx.send('❌ Database connection error.')
|
|
return
|
|
|
|
characters = await db.get_user_characters(ctx.author.id)
|
|
|
|
if not characters:
|
|
await ctx.send('You have no characters yet. Use `,oc create` to create one.')
|
|
return
|
|
|
|
embed = discord.Embed(
|
|
title='Your Characters',
|
|
color=discord.Color.blue()
|
|
)
|
|
|
|
for char in characters:
|
|
embed.add_field(
|
|
name=char['name'],
|
|
value=f"Trigger: `,{char['trigger']}`",
|
|
inline=False
|
|
)
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
@oc_group.command(name='info')
|
|
async def info(self, ctx, *, character_name):
|
|
"""Show character information"""
|
|
db = self.bot.db if hasattr(self.bot, 'db') else None
|
|
if not db:
|
|
await ctx.send('❌ Database connection error.')
|
|
return
|
|
|
|
# Find character by name
|
|
characters = await db.get_user_characters(ctx.author.id)
|
|
character = None
|
|
for char in characters:
|
|
if char['name'].lower() == character_name.lower():
|
|
character = char
|
|
break
|
|
|
|
if not character:
|
|
await ctx.send(f'❌ Character `{character_name}` not found.')
|
|
return
|
|
|
|
embed = discord.Embed(
|
|
title=character['name'],
|
|
color=discord.Color.purple()
|
|
)
|
|
embed.add_field(name='Trigger', value=f"`,{character['trigger']}`", inline=False)
|
|
embed.add_field(name='Avatar', value=f"[Link]({character['avatar_url']})", inline=False)
|
|
if character['description']:
|
|
embed.add_field(name='Description', value=character['description'], inline=False)
|
|
embed.add_field(name='Created', value=character['created_at'], inline=True)
|
|
embed.add_field(name='Last Used', value=character['last_used'] or 'Never', inline=True)
|
|
embed.set_thumbnail(url=character['avatar_url'])
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
@oc_group.command(name='edit')
|
|
async def edit(self, ctx, character_name=None):
|
|
"""Edit a character"""
|
|
db = self.bot.db if hasattr(self.bot, 'db') else None
|
|
if not db:
|
|
await ctx.send('❌ Database connection error.')
|
|
return
|
|
|
|
characters = await db.get_user_characters(ctx.author.id)
|
|
|
|
if not characters:
|
|
await ctx.send('You have no characters to edit.')
|
|
return
|
|
|
|
# If no character specified, show selection
|
|
if not character_name:
|
|
embed = discord.Embed(
|
|
title='Select a character to edit:',
|
|
color=discord.Color.blue()
|
|
)
|
|
for i, char in enumerate(characters, 1):
|
|
embed.add_field(
|
|
name=f'{i}. {char["name"]}',
|
|
value=f"Trigger: `,{char['trigger']}`",
|
|
inline=False
|
|
)
|
|
await ctx.send(embed=embed)
|
|
await ctx.send('Reply with the number of the character you want to edit.')
|
|
|
|
try:
|
|
msg = await self.bot.wait_for(
|
|
'message',
|
|
check=lambda m: m.author == ctx.author and m.channel == ctx.channel,
|
|
timeout=120
|
|
)
|
|
try:
|
|
idx = int(msg.content) - 1
|
|
if 0 <= idx < len(characters):
|
|
character = characters[idx]
|
|
else:
|
|
await ctx.send('❌ Invalid selection.')
|
|
return
|
|
except ValueError:
|
|
await ctx.send('❌ Invalid input.')
|
|
return
|
|
except asyncio.TimeoutError:
|
|
await ctx.send('❌ Edit cancelled.')
|
|
return
|
|
else:
|
|
# Find by name
|
|
character = None
|
|
for char in characters:
|
|
if char['name'].lower() == character_name.lower():
|
|
character = char
|
|
break
|
|
|
|
if not character:
|
|
await ctx.send(f'❌ Character `{character_name}` not found.')
|
|
return
|
|
|
|
# Show edit menu
|
|
embed = discord.Embed(
|
|
title=f'Editing: {character["name"]}',
|
|
color=discord.Color.gold()
|
|
)
|
|
embed.add_field(name='1', value='Edit Name', inline=False)
|
|
embed.add_field(name='2', value='Edit Avatar', inline=False)
|
|
embed.add_field(name='3', value='Edit Trigger', inline=False)
|
|
embed.add_field(name='4', value='Edit Description', inline=False)
|
|
await ctx.send(embed=embed)
|
|
|
|
try:
|
|
msg = await self.bot.wait_for(
|
|
'message',
|
|
check=lambda m: m.author == ctx.author and m.channel == ctx.channel,
|
|
timeout=120
|
|
)
|
|
choice = msg.content
|
|
except asyncio.TimeoutError:
|
|
await ctx.send('❌ Edit cancelled.')
|
|
return
|
|
|
|
updates = {}
|
|
|
|
if choice == '1':
|
|
new_value = await self.prompt_user(ctx, 'New name?')
|
|
if new_value:
|
|
updates['name'] = new_value
|
|
elif choice == '2':
|
|
new_value = await self.prompt_user(ctx, 'New avatar URL?')
|
|
if new_value:
|
|
updates['avatar_url'] = new_value
|
|
elif choice == '3':
|
|
while True:
|
|
new_value = await self.prompt_user(ctx, 'New trigger?')
|
|
if not new_value:
|
|
return
|
|
|
|
if new_value.lower() in RESERVED_TRIGGERS:
|
|
await ctx.send(f'❌ `{new_value}` is reserved.')
|
|
continue
|
|
|
|
if await db.is_trigger_available(ctx.author.id, new_value):
|
|
updates['trigger'] = new_value
|
|
break
|
|
else:
|
|
await ctx.send(f'❌ Trigger `,{new_value}` is already in use.')
|
|
elif choice == '4':
|
|
new_value = await self.prompt_user(ctx, 'New description?')
|
|
if new_value:
|
|
updates['description'] = new_value
|
|
|
|
if updates and await db.update_character(character['id'], **updates):
|
|
await ctx.send(f'✅ Character updated!')
|
|
else:
|
|
await ctx.send('❌ Failed to update character.')
|
|
|
|
@oc_group.command(name='delete')
|
|
async def delete(self, ctx, *, character_name):
|
|
"""Delete a character"""
|
|
db = self.bot.db if hasattr(self.bot, 'db') else None
|
|
if not db:
|
|
await ctx.send('❌ Database connection error.')
|
|
return
|
|
|
|
characters = await db.get_user_characters(ctx.author.id)
|
|
character = None
|
|
|
|
for char in characters:
|
|
if char['name'].lower() == character_name.lower():
|
|
character = char
|
|
break
|
|
|
|
if not character:
|
|
await ctx.send(f'❌ Character `{character_name}` not found.')
|
|
return
|
|
|
|
await ctx.send(f'Are you sure you want to delete **{character["name"]}**? Type `YES` within 30 seconds to confirm.')
|
|
|
|
try:
|
|
msg = await self.bot.wait_for(
|
|
'message',
|
|
check=lambda m: m.author == ctx.author and m.channel == ctx.channel,
|
|
timeout=30
|
|
)
|
|
if msg.content.upper() != 'YES':
|
|
await ctx.send('❌ Deletion cancelled.')
|
|
return
|
|
except asyncio.TimeoutError:
|
|
await ctx.send('❌ Deletion cancelled (timeout).')
|
|
return
|
|
|
|
if await db.delete_character(character['id']):
|
|
await ctx.send(f'✅ Character **{character["name"]}** deleted.')
|
|
else:
|
|
await ctx.send('❌ Failed to delete character.')
|
|
|
|
@oc_group.command(name='preview')
|
|
async def preview(self, ctx, *, character_name):
|
|
"""Preview how a character message will look"""
|
|
db = self.bot.db if hasattr(self.bot, 'db') else None
|
|
if not db:
|
|
await ctx.send('❌ Database connection error.')
|
|
return
|
|
|
|
characters = await db.get_user_characters(ctx.author.id)
|
|
character = None
|
|
|
|
for char in characters:
|
|
if char['name'].lower() == character_name.lower():
|
|
character = char
|
|
break
|
|
|
|
if not character:
|
|
await ctx.send(f'❌ Character `{character_name}` not found.')
|
|
return
|
|
|
|
await ctx.send('**Preview of how your character message will appear:**')
|
|
|
|
try:
|
|
webhooks = await ctx.channel.webhooks()
|
|
webhook = None
|
|
|
|
for wh in webhooks:
|
|
if wh.user.id == self.bot.user.id:
|
|
webhook = wh
|
|
break
|
|
|
|
if not webhook:
|
|
webhook = await ctx.channel.create_webhook(name='OC-Bot')
|
|
|
|
await webhook.send(
|
|
content='*This is a preview message*',
|
|
username=character['name'],
|
|
avatar_url=character['avatar_url']
|
|
)
|
|
except Exception as e:
|
|
logger.error(f'Preview error: {e}')
|
|
await ctx.send('❌ Failed to create preview.')
|
|
|
|
async def setup(bot):
|
|
await bot.add_cog(OC(bot))
|