import os # OS Locations import yaml # YAML parser for Bot config files import asyncio # Discord Py Dependency import discord # Main Lib from discord.ext import commands # Commands module from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash Command Library from discord_slash.utils.manage_commands import create_choice, create_option, create_permission # Slash Command features from discord_slash.model import SlashCommandPermissionType from discord_slash.client import SlashCommand import re from bot import configFile, yaml_load, yaml_dump, reloadCog, cogsDir, unloadCog, dataFile, lookupFile, gmFile class GameManagement(commands.Cog, name='Game Management'): def __init__(self, client): self.client = client conf = yaml_load(configFile) lookup = yaml_load(lookupFile) data = yaml_load(dataFile) guild_ids= [ int(x) for x in list(lookup)] ### Move delete, Modify, and Reset commands to a separate secondary cog to enable when games exist? @cog_ext.cog_subcommand( base='game', # subcommand_group='', name='delete', description='Deletes a game role and accompanying category, text, voice channels, and data.', # base_description='Commands for setting up and removing games on the Guild.', # base_default_permission=False, # base_permissions=permissions, # subcommand_group_description='Adds a time slot available to the channel for games.', guild_ids=guild_ids, options=[ create_option( name='game_role', description='The role representing the game you want to interact with.', option_type=8, required=True ) ] ) async def _game_delete(self, ctx:SlashContext, game_role:discord.Role): await ctx.channel.trigger_typing() conf = yaml_load(configFile) data = yaml_load(dataFile) gms = yaml_load(gmFile) lookup = yaml_load(lookupFile) guildStr = str(ctx.guild.id) if str(game_role.id) not in lookup[guildStr]: await ctx.send(f'```This is not a valid game role. Please mention a role that is associated with a game.```') return game_title = lookup[guildStr][str(game_role.id)]['game_title'] time = lookup[guildStr][str(game_role.id)]['time'] c = ctx.guild.get_channel(lookup[guildStr][str(game_role.id)]['category']) gm = lookup[guildStr][str(game_role.id)]['gm'] channelsFound = False for g in list(data[guildStr][time]): if game_role.id in g.values(): data[guildStr][time].remove(g) break if c is not None: channelsFound = True for t in ctx.guild.text_channels: if t.category == c: await t.delete(reason=f'/game delete command issued by `{ctx.author.display_name}`') for v in ctx.guild.voice_channels: if v.category == c: await v.delete(reason=f'/game delete command issued by `{ctx.author.display_name}`') await c.delete(reason=f'/game delete command issued by `{ctx.author.display_name}`') lookup[guildStr].pop(str(game_role.id), None) gm_m = await ctx.guild.fetch_member(gm) output = f'The game `{game_title}` for timeslot `{conf[guildStr]["timeslots"][time]}` and with GM `{gm_m.display_name}` has been deleted.' if channelsFound: output = ''.join([output,' All associated text, voice, and category channels have been deleted.']) else: output = ''.join([output,' No associated text, voice, or category channels were found. Please delete them manually if they still persist.']) await ctx.send(f'```{output}```') await game_role.delete(reason=f'/game delete command issued by `{ctx.author.display_name}`') gms[guildStr][str(gm)].remove(game_role.id) if not gms[guildStr][str(gm)]: gms[guildStr].pop(str(gm)) yaml_dump(lookup, lookupFile) yaml_dump(data, dataFile) yaml_dump(gms, gmFile) if not any([x for x in yaml_load(lookupFile).values()]): unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') await self.client.slash.sync_all_commands() @cog_ext.cog_subcommand( base='game', # subcommand_group='', name='modify', description='Edit the information of an existing game channel.', # base_description='Commands for setting up and removing games on the Guild.', # base_default_permission=False, # base_permissions=permissions, # subcommand_group_description='Adds a time slot available to the channel for games.', guild_ids=guild_ids, options=[ create_option( name='game_role', description='The role of the game you are trying to edit.', option_type=8, required=True, ), create_option( name='timeslot', description='The new timeslot, if you are changing timeslots.', option_type=3, required=False ), create_option( name='gm', description='The new GM, if the GM is changing.', option_type=6, required=False ), create_option( name='max_players', description='The maximum number of players the game can take.', option_type=4, required=False ), create_option( name='game_title', description='The new title if the title is changing.', option_type=3, required=False ), create_option( name='min_players', description='The minimum number of players the gane can take.', option_type=4, required=False ), create_option( name='reserved_spaces', description='The number of spaces the GM is reserving in the game.', option_type=4, required=False ), create_option( name='system', description='What system the game is using.', option_type=3, required=False ), create_option( name='platform', description='What platform the game will be running on.', option_type=3, required=False ) ] ) async def _game_modify( self, ctx:SlashContext, game_role:discord.Role, timeslot:str=None, gm:discord.User=None, max_players:int=None, game_title:str=None, min_players:int=None, reserved_spaces:int=None, system:str=None, platform:str=None ): await ctx.channel.trigger_typing() if all(x is None for x in [timeslot, gm, max_players, game_title, min_players, reserved_spaces, system, platform]): await ctx.send(f'```No parameters have been entered to modify the game.```') return conf = yaml_load(configFile) data = yaml_load(dataFile) gms = yaml_load(gmFile) lookup = yaml_load(lookupFile) guildStr = str(ctx.guild.id) if guildStr not in lookup: lookup[guildStr] = {} if guildStr not in data: data[guildStr] = {} r = game_role if timeslot is not None: time = re.sub(r"\W+",'', timeslot[:9].lower()) if time not in conf[guildStr]['timeslots']: await ctx.send(f'```Time code `{timeslot}` is not recognised. Please enter a valid time code to register the game. use `/config timeslots list` to get a list of valid time codes.```') return if time not in data[guildStr]: data[guildStr][time] = [] else: time = lookup[guildStr][str(r.id)]['time'] g_title = game_title if game_title is not None else lookup[guildStr][str(r.id)]['game_title'] if str(r.id) not in lookup[guildStr]: await ctx.send(f'```This is not a valid game role. Please mention a role that is associated with a game.```') return if 'roles' not in conf[guildStr]: conf[guildStr]['roles'] = {} if 'bot' not in conf[guildStr]['roles']: await ctx.send(f'```\`Bot` role for guild `{ctx.guild.name}` has not been defined. Cannot configure game.```') return if 'timeslots' not in conf[guildStr]: conf[guildStr]['timeslots'] = {} if any(x is not None and x < 0 for x in [min_players, max_players, reserved_spaces]): await ctx.send(f'```You cannot enter negative integers for the number of players.```') return if min_players is not None and max_players is not None: if min_players > max_players: await ctx.send(f'```The minimum number of players cannot exceed the maximum number of players.```') return if reserved_spaces is not None and max_players is not None: if reserved_spaces > max_players: await ctx.send(f'```The number of reserved spaces cannot exceed the maximum number of players.```') return if game_title is not None: if game_title in [x['game_title'] for x in lookup[guildStr].values()] and time in [x['time'] for x in lookup[guildStr].values()]: await ctx.send(f'```The target game `{game_title}` has already been created for the time slot `{conf[guildStr]["timeslots"][time]}`. Please avoud duplicates, or use the `modify` sub-command to edit the existing game.```') return mc = gm if gm is not None else ctx.guild.get_member(lookup[guildStr][str(r.id)]['gm']) #### Retrieve Old Values oldTime = lookup[guildStr][str(r.id)]['time'] for g in data[guildStr][oldTime]: if game_role.id in g.values(): oldDict = g.copy() break gDict = { 'game_title': game_title, 'gm': mc.id, 'max_players': max_players, 'min_players': min_players, 'current_players': reserved_spaces, 'system': system, 'platform': platform, # 'role': r.id, # 'category': c.id, # 'text_channel': t.id, # 'header_message': o.id } d = [k for k in gDict if gDict[k] != oldDict[k] and gDict[k] != None] result = f'The game {g_title} has been updated.\n' if time != oldTime: for g in list(data[guildStr][oldTime]): if game_role.id in g.values(): data[guildStr][oldTime].remove(g) break data[guildStr][oldTime].pop(str(r.id), None) result = ''.join([result,f'The time slot for the game has been updated to {conf[guildStr]["timeslots"][time]}.\n']) if 'gm' in d: if r not in mc.roles: await mc.add_roles(r) if guildStr not in gms: gms[guildStr] = {} if str(mc.id) not in gms[guildStr]: gms[guildStr][str(gm.id)] = [] gms[guildStr][str(mc.id)].append(r.id) gms[guildStr][str(lookup[guildStr][str(r.id)]['gm'])].remove(r.id) if not gms[guildStr][str(lookup[guildStr][str(r.id)]['gm'])]: gms[guildStr].pop(str(lookup[guildStr][str(r.id)]['gm']), None) result = ''.join([result,f'The GM has been updated to {mc.display_name}.\n']) c = await self.client.fetch_channel(lookup[guildStr][str(r.id)]['category']) permissions = { ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False), r: discord.PermissionOverwrite(read_messages=True), ctx.guild.get_role(conf[guildStr]['roles']['bot']): discord.PermissionOverwrite(read_messages=True), gm: discord.PermissionOverwrite( read_messages=True, manage_messages=True, manage_channels=True, manage_permissions=True, priority_speaker=True, move_members=True, mute_members=True, deafen_members=True ) } print('So far so good.') if time != oldTime or 'game_title' in d: await r.edit( name=f'{time.upper()}: {g_title}', reason=f'/game modify command issued by `{ctx.author.display_name}`', mentionable=True, permissions=discord.Permissions.none(), ) result = ''.join([result,f'The names of the role and channel category have been updated to match the new {"game title" if "game_title" in d and time == oldTime else "time slot" if "game_title" not in d and time != oldTime else "game title and time slot"}.\n']) if c is None: c = await ctx.guild.create_category( name=f'{time.upper()}: {g_title}', overwrites=permissions, reason=f'/game modify command issued by `{ctx.author.display_name}`' ) await c.create_voice_channel( name=f'voice: {g_title}', topic=f'Default voice channel for the game `{g_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{mc.display_name}`.', reason=f'/game create command issued by `{ctx.author.display_name}`' ) t = await c.create_text_channel( name=f'text: {g_title}', topic=f'Default text channel for the game `{g_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{mc.display_name}`.', reason=f'/game create command issued by `{ctx.author.display_name}`' ) else: print(type(c)) await c.edit( name=f'{time.upper()}: {g_title}', overwrites=permissions, reason=f'/game modify command issued by `{ctx.author.display_name}`', ) tPos = len(ctx.guild.channels) t = None v = False for tc in c.text_channels: if tc.position <= tPos: t, tPos = tc, tc.position await t.edit( sync_permissions=True, reason=f'/game modify command issued by `{ctx.author.display_name}`' ) for vc in c.voice_channels: await vc.edit( sync_permissions=True, reason=f'/game modify command issued by `{ctx.author.display_name}`' ) v = True if t is None: t = await c.create_text_channel( name=f'text: {g_title}', topic=f'Default text channel for the game `{g_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{mc.display_name}`.', reason=f'/game modify command issued by `{ctx.author.display_name}`' ) else: hm = await t.fetch_message(oldDict['header_message']) if hm is not None: await hm.delete() else: pins = await t.pins if pins: hm = discord.utils.find(lambda x: x.text.startswith('```Hello ' and 'Your game channels for ' in x.text and x.author.id == self.client.user.id), pins) if hm is not None: await hm.delete() if not v: await c.create_voice_channel( name=f'voice: {g_title}', topic=f'Default voice channel for the game `{g_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{mc.display_name}`.', reason=f'/game modify command issued by `{ctx.author.display_name}`' ) result = ''.join([result,f'The game has space for {max_players} players (with {reserved_spaces if reserved_spaces is not None else str(0)} currently occupied).\n']) sys = system if system is not None else oldDict['system'] max_p = max_players if max_players is not None else oldDict['max_players'] min_p = min_players if min_players is not None else oldDict['min_players'] cur_p = reserved_spaces if reserved_spaces is not None else oldDict['current_players'] plat = platform if platform is not None else oldDict['platform'] output = f'```Hello {mc.display_name}! Your game channels for `{g_title}` have been created.\nYou can ping your players or edit the game settings by interacting with the game role through the Bot commands. Have fun!```\n' output = ''.join([output,f'```Game Title: {g_title}\n']) output = ''.join([output,f'GM: {mc.display_name}\n']) output = ''.join([output,f'Time: {conf[guildStr]["timeslots"][time]}\n']) output = ''.join([output,f'System: {sys}\n'if sys is not None else '']) output = ''.join([output,f'Max Players: {max_p}\n']) output = ''.join([output,f'Min Players: {min_p}\n'if min_p is not None else '']) output = ''.join([output,f'Current Players: {cur_p}\n' if cur_p is not None else '0\n']) output = ''.join([output,f'Platform: {plat}```' if plat is not None else '```']) result = ''.join(['```',result,f'```\n{mc.mention} {r.mention} {t.mention}']) await ctx.send(result) o = await t.send(output) await o.pin(reason=f'/game modift command issued by `{ctx.author.display_name}`') gDict = { 'game_title': g_title, 'gm': mc.id, 'max_players': max_p, 'min_players': min_p, 'current_players': cur_p, 'system': sys, 'platform': plat, 'role': r.id, 'category': c.id, 'text_channel': t.id, 'header_message': o.id } if time != oldTime: data[guildStr][time].append(gDict) lookup[guildStr][str(r.id)] = { 'category': c.id, 'gm': mc.id, 'time': time, 'game_title': g_title } yaml_dump(data,dataFile) yaml_dump(lookup,lookupFile) yaml_dump(gms,gmFile) def setup(client): client.add_cog(GameManagement(client))