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 typing import re from bot import configFile, yaml_load, yaml_dump, reloadCog, cogsDir, unloadCog, dataFile, lookupFile, gmFile, loadCog #### Game Role and Channel Setup Command class GameSetup(commands.Cog, name='Game Setup'): def __init__(self, client): self.client = client conf=yaml_load(configFile) permissions={} guild_ids = list(set.union(set([int(guildKey) for guildKey in yaml_load(configFile) if len(yaml_load(configFile)[guildKey]['timeslots']) > 0]),set([int(guildKey) for guildKey in yaml_load(configFile) if type(yaml_load(configFile)[guildKey]['roles']['bot']) is int]))) for guildID in guild_ids: permissions[guildID] = [] permissions[guildID].append(create_permission(id=conf[str(guildID)]['owner'],id_type=SlashCommandPermissionType.USER,permission=True)) for admin in conf[str(guildID)]['roles']['admin']: permissions[guildID].append(create_permission(id=admin,id_type=SlashCommandPermissionType.ROLE,permission=True)) permissions[guildID] = [] permissions[guildID].append(create_permission(id=conf[str(guildID)]['owner'],id_type=SlashCommandPermissionType.USER,permission=True)) for admin in conf[str(guildID)]['roles']['admin']: permissions[guildID].append(create_permission(id=admin,id_type=SlashCommandPermissionType.ROLE,permission=True)) @cog_ext.cog_subcommand( base='game', # subcommand_group='', name='create', description='Create a new game role and accompanying category, text, and voice channels.', 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='timeslot', description='The timeslot code for when the game will run.', option_type=3, required=True ), create_option( name='gm', description='The person who will be running the game.', option_type=6, required=True ), create_option( name='max_players', description='The maximum number of players the game can take.', option_type=4, required=True ), create_option( name='game_title', description='What the game is called.', option_type=3, required=True ), create_option( name='min_players', description='The minimum number of players the game 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_create( self, ctx:SlashContext, timeslot:str, gm:discord.Member, max_players:int, game_title:str, min_players: typing.Optional[int]=None, reserved_spaces: typing.Optional[int]=None, system:typing.Optional[str]= None, platform:typing.Optional[str]=None ): 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) time = re.sub(r"\W+",'', timeslot[:9].lower()) 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 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 min_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: if reserved_spaces > max_players: await ctx.send(f'```The number of reserved spaces cannot exceed the maximum number of players.```') return 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 guildStr not in lookup: lookup[guildStr] = {} 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'```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 if guildStr not in data: data[guildStr] = {} if time not in data[guildStr]: data[guildStr][time] = [] rExists, cExists = False, False r = discord.utils.get(ctx.guild.roles, name=f'{time.upper()}: {game_title}') if r is None: r = await ctx.guild.create_role( name=f'{time.upper()}: {game_title}', reason=f'/game create command issued by `{ctx.author.display_name}`', mentionable=True, permissions=discord.Permissions.none(), colour=discord.Colour.green() ) else: rExists = True await r.edit( mentionable=True, permissions=discord.Permissions.none(), reason=f'/game create command issued by `{ctx.author.display_name}`', colour=discord.Colour.green() ) if r not in gm.roles: await gm.add_roles(r) 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 ) } c = discord.utils.get(ctx.guild.categories, name=f'{time.upper()}: {game_title}') if c is None: c = await ctx.guild.create_category( name=f'{time.upper()}: {game_title}', overwrites=permissions, reason=f'/game create command issued by `{ctx.author.display_name}`' ) await c.create_voice_channel( name=f'voice: {game_title}', topic=f'Default voice channel for the game `{game_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{gm.display_name}`.', reason=f'/game create command issued by `{ctx.author.display_name}`' ) t = await c.create_text_channel( name=f'text: {game_title}', topic=f'Default text channel for the game `{game_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{gm.display_name}`.', reason=f'/game create command issued by `{ctx.author.display_name}`' ) else: cExists= True await c.edit( overwrites=permissions, reason=f'/game create 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 create command issued by `{ctx.author.display_name}`' ) for vc in c.voice_channels: await vc.edit( sync_permissions=True, reason=f'/game create command issued by `{ctx.author.display_name}`' ) v = True if t is None: t = await c.create_text_channel( name=f'text: {game_title}', topic=f'Default text channel for the game `{game_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{gm.display_name}`.', reason=f'/game create command issued by `{ctx.author.display_name}`' ) 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: {game_title}', topic=f'Default voice channel for the game `{game_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{gm.display_name}`.', reason=f'/game create command issued by `{ctx.author.display_name}`' ) result = f'Game `{game_title}` has been created for timeslot `{conf[guildStr]["timeslots"][time]}`` with GM `{gm.display_name}` and space for {max_players} players (with {reserved_spaces if reserved_spaces is not None else str(0)} currently occupied).\n' if not rExists: result = ''.join([result,f'There was already a role that matched the game, so that role has been reconfigured.\n\nNote: Editing this role will synchronise changes with the game channels, and deleting the role will delete the game and all its data.\n\n']) else: result = ''.join([result,f'A role for the game has been created.\n']) if not cExists: result = ''.join([result,f'There was already a channel category that matched the game, so it has been reconfigured with the appropriate permissions and text and voice channels.\n']) else: result = ''.join([result,f'A channel category with the appropriate text and voice channels has been created.\n']) result = ''.join(['```',result,f'```\n{gm.mention} {r.mention} {t.mention}']) output = f'```Hello {gm.display_name}! Your game channels for `{game_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: {game_title}\n']) output = ''.join([output,f'GM: {gm.display_name}\n']) output = ''.join([output,f'Time: {conf[guildStr]["timeslots"][time]}\n']) output = ''.join([output,f'System: {system}\n'if system is not None else '']) output = ''.join([output,f'Max Players: {max_players}\n']) output = ''.join([output,f'Min Players: {min_players}\n'if min_players is not None else '']) output = ''.join([output,f'Current Players: {reserved_spaces}\n' if reserved_spaces is not None else '0\n']) output = ''.join([output,f'Platform: {platform}```' if platform is not None else '```']) await ctx.send(result) o = await t.send(output) await o.pin(reason=f'/game create command issued by `{ctx.author.display_name}`') gDict = { 'game_title': game_title, 'gm': gm.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 } data[guildStr][time].append(gDict) lookup[guildStr][str(r.id)] = { 'category': c.id, 'gm': gm.id, 'time': time, 'game_title': game_title } if guildStr not in gms: gms[guildStr] = {} if str(gm.id) not in gms[guildStr]: gms[guildStr][str(gm.id)] = [] gms[guildStr][str(gm.id)].append(r.id) yaml_dump(data,dataFile) yaml_dump(lookup,lookupFile) yaml_dump(gms,gmFile) if all([x for x in yaml_load(lookupFile).values()]): if self.client.get_cog('Game Management') is None: loadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') await self.client.slash.sync_all_commands() # Enable the Game Management commands def setup(client): client.add_cog(GameSetup(client))