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, cogsDir, unloadCog, dataFile, lookupFile, gmFile, loadCog, categoriesFile #### Game Role and Channel Setup Command class GameCreate(commands.Cog, name='Game Create'): def __init__(self, client): self.client = client conf=yaml_load(configFile) permissions={} guild_ids = list(set.intersection(set([int(guildKey) for guildKey in yaml_load(configFile) if yaml_load(configFile)[guildKey]['timeslots']]),set([int(guildKey) for guildKey in yaml_load(configFile) if 'bot' in yaml_load(configFile)[guildKey]['roles'] and 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='current_players', description='The number of players currently in this 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, current_players: 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) categories = yaml_load(categoriesFile) 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'```Error: `Bot` role for guild `{ctx.guild.name}` has not been defined. Cannot configure game.```',hidden=True) return if 'timeslots' not in conf[guildStr]: conf[guildStr]['timeslots'] = {} if time not in conf[guildStr]['timeslots']: await ctx.send(f'```Error: 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.```',hidden=True) return if min_players and min_players > max_players: await ctx.send(f'```Error: The minimum number of players cannot exceed the maximum number of players.```',hidden=True) return if current_players and current_players > max_players: await ctx.send(f'```Error: The number of reserved spaces cannot exceed the maximum number of players.```',hidden=True) return if any(x is not None and x < 0 for x in [min_players, max_players, current_players]): await ctx.send(f'```Error: You cannot enter negative integers for the number of players.```',hidden=True) return if guildStr not in lookup: lookup[guildStr] = {} if game_title in [x['game_title'] for x in lookup[str(ctx.guild.id)].values()] and time in [x['time'] for x in lookup[str(ctx.guild.id)].values()]: await ctx.send(f'```Error: 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.```',hidden=True) 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 not r: 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 not c: 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 not t: 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.content.startswith('```Hello ') and 'Your game channels for ' in x.content 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 {current_players if current_players else "0"} currently occupied).\n' if 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\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']) if 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: {current_players if current_players else "0"}\n']) output = ''.join([output,f'Platform: {platform}```' if platform is not None else '```']) output = ''.join([output,f'\n\n{gm.mention} | {r.mention}']) await ctx.send(result,hidden=True) o = await t.send(output) await o.pin(reason=f'/game create command issued by `{ctx.author.display_name}`') data[guildStr][time][str(r.id)] = { 'game_title': game_title, 'gm': gm.id, 'max_players': max_players, 'min_players': min_players, 'current_players': current_players if current_players is not None else 0, 'system': system, 'platform': platform, 'role': r.id, 'category': c.id, 'text_channel': t.id, 'header_message': o.id } lookup[guildStr][str(r.id)] = { 'category': c.id, 'gm': gm.id, 'time': time, 'game_title': game_title, 'text_channel': t.id } if guildStr not in gms: gms[guildStr] = {} if str(gm.id) not in gms[guildStr]: gms[guildStr][str(gm.id)] = [] if str(guildStr) not in categories: categories[guildStr] = {} gms[guildStr][str(gm.id)].append(r.id) if guildStr not in categories: categories[guildStr] = {} categories[guildStr][str(c.id)] = r.id yaml_dump(data,dataFile) yaml_dump(lookup,lookupFile) yaml_dump(gms,gmFile) yaml_dump(categories,categoriesFile) # Enable the Game Management and Player commands if any([x for x in yaml_load(lookupFile).values()]): flag = False if self.client.get_cog('Game Management') is None: loadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') flag = True if self.client.get_cog('Player Commands') is None: loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py') flag = True if self.client.get_cog('T-Card Command') is None: loadCog(f'./{cogsDir}/slashcommands/secondary/tcard.py') flag = True if self.client.get_cog('Pitch Command') is None: loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py') flag = True if flag: await self.client.slash.sync_all_commands() def setup(client): client.add_cog(GameCreate(client))