""" Discord Utilities This module contains Discord helper functions for channels, roles, embeds, and other Discord-specific operations. """ import logging import os import asyncio from typing import Optional import discord from discord.ext import commands from helpers.constants import SBA_COLOR, PD_SEASON, IMAGES logger = logging.getLogger('discord_app') async def send_to_bothole(ctx, content, embed): """Send a message to the pd-bot-hole channel.""" await discord.utils.get(ctx.guild.text_channels, name='pd-bot-hole') \ .send(content=content, embed=embed) async def send_to_news(ctx, content, embed): """Send a message to the pd-news-ticker channel.""" await discord.utils.get(ctx.guild.text_channels, name='pd-news-ticker') \ .send(content=content, embed=embed) async def typing_pause(ctx, seconds=1): """Show typing indicator for specified seconds.""" async with ctx.typing(): await asyncio.sleep(seconds) async def pause_then_type(ctx, message): """Show typing indicator based on message length, then send message.""" async with ctx.typing(): await asyncio.sleep(len(message) / 100) await ctx.send(message) async def check_if_pdhole(ctx): """Check if the current channel is pd-bot-hole.""" if ctx.message.channel.name != 'pd-bot-hole': await ctx.send('Slide on down to my bot-hole for running commands.') await ctx.message.add_reaction('❌') return False return True async def bad_channel(ctx): """Check if current channel is in the list of bad channels for commands.""" bad_channels = ['paper-dynasty-chat', 'pd-news-ticker'] if ctx.message.channel.name in bad_channels: await ctx.message.add_reaction('❌') bot_hole = discord.utils.get( ctx.guild.text_channels, name=f'pd-bot-hole' ) await ctx.send(f'Slide on down to the {bot_hole.mention} ;)') return True else: return False def get_channel(ctx, name) -> Optional[discord.TextChannel]: """Get a text channel by name.""" # Handle both Context and Interaction objects guild = ctx.guild if hasattr(ctx, 'guild') else None if not guild: return None channel = discord.utils.get( guild.text_channels, name=name ) if channel: return channel return None async def get_emoji(ctx, name, return_empty=True): """Get an emoji by name, with fallback options.""" try: emoji = await commands.converter.EmojiConverter().convert(ctx, name) except: if return_empty: emoji = '' else: return name return emoji async def react_and_reply(ctx, reaction, message): """Add a reaction to the message and send a reply.""" await ctx.message.add_reaction(reaction) await ctx.send(message) async def send_to_channel(bot, channel_name, content=None, embed=None): """Send a message to a specific channel by name or ID.""" guild = bot.get_guild(int(os.environ.get('GUILD_ID'))) if not guild: logger.error('Cannot send to channel - bot not logged in') return this_channel = discord.utils.get(guild.text_channels, name=channel_name) if not this_channel: this_channel = discord.utils.get(guild.text_channels, id=channel_name) if not this_channel: raise NameError(f'**{channel_name}** channel not found') return await this_channel.send(content=content, embed=embed) async def get_or_create_role(ctx, role_name, mentionable=True): """Get an existing role or create it if it doesn't exist.""" this_role = discord.utils.get(ctx.guild.roles, name=role_name) if not this_role: this_role = await ctx.guild.create_role(name=role_name, mentionable=mentionable) return this_role def get_special_embed(special): """Create an embed for a special item.""" embed = discord.Embed(title=f'{special.name} - Special #{special.get_id()}', color=discord.Color.random(), description=f'{special.short_desc}') embed.add_field(name='Description', value=f'{special.long_desc}', inline=False) if special.thumbnail.lower() != 'none': embed.set_thumbnail(url=f'{special.thumbnail}') if special.url.lower() != 'none': embed.set_image(url=f'{special.url}') return embed def get_random_embed(title, thumb=None): """Create a basic embed with random color.""" embed = discord.Embed(title=title, color=discord.Color.random()) if thumb: embed.set_thumbnail(url=thumb) return embed def get_team_embed(title, team=None, thumbnail: bool = True): """Create a team-branded embed.""" if team: embed = discord.Embed( title=title, color=int(team["color"], 16) if team["color"] else int(SBA_COLOR, 16) ) embed.set_footer(text=f'Paper Dynasty Season {team["season"]}', icon_url=IMAGES['logo']) if thumbnail: embed.set_thumbnail(url=team["logo"] if team["logo"] else IMAGES['logo']) else: embed = discord.Embed( title=title, color=int(SBA_COLOR, 16) ) embed.set_footer(text=f'Paper Dynasty Season {PD_SEASON}', icon_url=IMAGES['logo']) if thumbnail: embed.set_thumbnail(url=IMAGES['logo']) return embed async def create_channel_old( ctx, channel_name: str, category_name: str, everyone_send=False, everyone_read=True, allowed_members=None, allowed_roles=None): """Create a text channel with specified permissions (legacy version).""" this_category = discord.utils.get(ctx.guild.categories, name=category_name) if not this_category: raise ValueError(f'I couldn\'t find a category named **{category_name}**') overwrites = { ctx.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True), ctx.guild.default_role: discord.PermissionOverwrite(read_messages=everyone_read, send_messages=everyone_send) } if allowed_members: if isinstance(allowed_members, list): for member in allowed_members: overwrites[member] = discord.PermissionOverwrite(read_messages=True, send_messages=True) if allowed_roles: if isinstance(allowed_roles, list): for role in allowed_roles: overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=True) this_channel = await ctx.guild.create_text_channel( channel_name, overwrites=overwrites, category=this_category ) logger.info(f'Creating channel ({channel_name}) in ({category_name})') return this_channel async def create_channel( ctx, channel_name: str, category_name: str, everyone_send=False, everyone_read=True, read_send_members: list = None, read_send_roles: list = None, read_only_roles: list = None): """Create a text channel with specified permissions.""" # Handle both Context and Interaction objects guild = ctx.guild if hasattr(ctx, 'guild') else None if not guild: raise ValueError(f'Unable to access guild from context object') # Get bot member - different for Context vs Interaction if hasattr(ctx, 'me'): # Context object bot_member = ctx.me elif hasattr(ctx, 'client'): # Interaction object bot_member = guild.get_member(ctx.client.user.id) else: # Fallback - try to find bot member by getting the first member with bot=True bot_member = next((m for m in guild.members if m.bot), None) if not bot_member: raise ValueError(f'Unable to find bot member in guild') this_category = discord.utils.get(guild.categories, name=category_name) if not this_category: raise ValueError(f'I couldn\'t find a category named **{category_name}**') overwrites = { bot_member: discord.PermissionOverwrite(read_messages=True, send_messages=True), guild.default_role: discord.PermissionOverwrite(read_messages=everyone_read, send_messages=everyone_send) } if read_send_members: for member in read_send_members: overwrites[member] = discord.PermissionOverwrite(read_messages=True, send_messages=True) if read_send_roles: for role in read_send_roles: overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=True) if read_only_roles: for role in read_only_roles: overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=False) this_channel = await guild.create_text_channel( channel_name, overwrites=overwrites, category=this_category ) logger.info(f'Creating channel ({channel_name}) in ({category_name})') return this_channel