diff --git a/TODO.md b/TODO.md index 738ccd3..0f035e8 100644 --- a/TODO.md +++ b/TODO.md @@ -10,7 +10,7 @@ - [x] Setup minimally functioning configs of guild on startup - [x] Synchronise core configuration `/commands` on startup -- [ ] ~~Synchronise secondary `/commands` on complete configuration **(see below)**~~ +- [ ] ~~Synchronise secondary `/commands` on complete configuration~~ ``(see below)`` ## Bot Functionality and Processes @@ -19,7 +19,7 @@ - [x] Infer Permissions from Config - [x] Dynamic Command Prefixes - [ ] Infer current games from Server Structure -**Create a separate cog to do this instead of having a migrate command, install the cog temporarily and remove the cog once migration is done.** +`Create a separate cog to do this instead of having a migrate command, install the cog temporarily and remove the cog once migration is done.` - [ ] Re-enable logging - [x] Delete Dev/Test Functions - [x] Error handlers @@ -31,8 +31,8 @@ > - [x] Add Config key for Help Channel -- [ ] Slash Command Buttons or -- [ ] Reaction listener selectors +- [ ] ~~Slash Command Buttons or~~ `This kind of got subsumed into other features.` +- [ ] ~~Reaction listener selectors~~ `So did this.` - [ ] Member Verification > - [x] Add Config key membership signup channels @@ -45,7 +45,7 @@ > - [ ] Message Receive Listener > - [ ] Membership Validation Listener -- [ ] Re-synchronise commands after any relevant config changes **(see above)** +- [ ] Re-synchronise commands after any relevant config changes `(See from above)` > - [ ] Role Delete (member, admin, game) > - [ ] Channel delete (notifications, logs, game text channel) @@ -93,25 +93,26 @@ Do the opposite: block deleting timeslots with existing games. - [x] Set up command permissions > - [x] Slash Commands + >> - [x] Admin Commands >> - [x] Game Management Commands > - [x] Native Bot Commands -- [ ] Migrate existing bot commands +- [x] Migrate existing bot commands > - [x] setupgame > - [x] ~~definebotrole~~ config > - [x] deletegame > - [x] ~~reset~~ purge -> - [ ] ~~migrate~~ **See above** +> - [ ] ~~migrate~~ `See above` > - [x] ~~kickplayer~~ `/player remove` > - [x] ~~addplayer~~ `/player add` > - [x] ~~leavegame~~ `/player leave` -> - [ ] Pitch command and sub-commands +> - [x] Pitch command and sub-commands -> > - [ ] run -> > - [ ] clear +> > - [ ] ~~run~~ `Combined both sub-commands into single command and prompt response.` +> > - [ ] ~~clear~~ ## Misc @@ -122,4 +123,4 @@ Do the opposite: block deleting timeslots with existing games. > - [ ] COMMANDS.md > - [ ] resources.md -- [ ] Make sure to document **not using discord_components and staying with discord-py-slash-commands library alone**. +- [ ] Make sure to document `not using discord_components and staying with discord-py-slash-commands library alone`. diff --git a/app/bot.py b/app/bot.py index 95f0203..b195af5 100644 --- a/app/bot.py +++ b/app/bot.py @@ -54,6 +54,9 @@ categoriesFile = os.getenv('CATEGORIES') if os.getenv('CATEGORIES').endswith('.y if not os.path.exists(categoriesFile): yaml_dump({},categoriesFile) +l = [dataFile, lookupFile, gmFile, categoriesFile] +if len(set(l)) != len(l): raise Exception('Config Error: there is a clash between two file names.') + # Locate Cogs Directory cogsDir = 'cogs' @@ -267,14 +270,15 @@ loadCogs('events') loadCogs('botcommands') loadCogs('slashcommands') if yaml_load(configFile): - if any([len(yaml_load(configFile)[x]['timeslots']) > 0 for x in yaml_load(configFile)]): + if any([yaml_load(configFile)[x]['timeslots'] for x in yaml_load(configFile)]): loadCog(f'./{cogsDir}/slashcommands/secondary/manipulate_timeslots.py') if any(['bot' in yaml_load(configFile)[x]['roles'] for x in yaml_load(configFile)]): - loadCog(f'./{cogsDir}/slashcommands/secondary/game_setup.py') + loadCog(f'./{cogsDir}/slashcommands/secondary/game_create.py') if yaml_load(lookupFile): - if any([len(x) > 0 for x in yaml_load(lookupFile).values()]): + if any([x for x in yaml_load(lookupFile).values()]): loadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py') + loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py') if any([len(yaml_load(configFile)[x]['membership']) > 0 for x in yaml_load(configFile)]): loadCog(f'./{cogsDir}/slashcommands/secondary/edit_membership.py') diff --git a/app/cogs/slashcommands/config.py b/app/cogs/slashcommands/config.py index 1ffd33c..8007e15 100644 --- a/app/cogs/slashcommands/config.py +++ b/app/cogs/slashcommands/config.py @@ -21,8 +21,7 @@ class Configuration(commands.Cog, name='Configuration Commands'): for gStr in conf: permissions[int(gStr)] = [] permissions[int(gStr)].append(create_permission(id=conf[gStr]['owner'],id_type=SlashCommandPermissionType.USER,permission=True)) - for admin in conf[gStr]['roles']['admin']: - permissions[int(gStr)].append(create_permission(id=admin,id_type=SlashCommandPermissionType.ROLE,permission=True)) + for admin in conf[gStr]['roles']['admin']: permissions[int(gStr)].append(create_permission(id=admin,id_type=SlashCommandPermissionType.ROLE,permission=True)) @cog_ext.cog_subcommand( base='config', @@ -110,13 +109,19 @@ class Configuration(commands.Cog, name='Configuration Commands'): if any(['bot' in yaml_load(configFile)[x]['roles'] for x in yaml_load(configFile)]): if any([yaml_load(configFile)[x]['timeslots'] for x in yaml_load(configFile)]): flag = False - if self.client.get_cog('Game Setup') is None: - loadCog(f'./{cogsDir}/slashcommands/secondary/game_setup.py') + if self.client.get_cog('Game Create') is None: + loadCog(f'./{cogsDir}/slashcommands/secondary/game_create.py') flag = True if any([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') 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('Pitch') is None: + loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py') + flag = True if flag: await self.client.slash.sync_all_commands() @cog_ext.cog_subcommand( @@ -281,14 +286,20 @@ class Configuration(commands.Cog, name='Configuration Commands'): loadCog(f'./{cogsDir}/slashcommands/secondary/manipulate_timeslots.py') flag = True if any(['bot' in yaml_load(configFile)[x]['roles'] for x in yaml_load(configFile)]): - if self.client.get_cog('Game Setup') is None: - loadCog(f'./{cogsDir}/slashcommands/secondary/game_setup.py') + if self.client.get_cog('Game Create') is None: + loadCog(f'./{cogsDir}/slashcommands/secondary/game_create.py') Flag = True if yaml_load(lookupFile): if any([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') 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('Pitch') is None: + loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py') + Flag = True if flag: await self.client.slash.sync_all_commands() @cog_ext.cog_subcommand( diff --git a/app/cogs/slashcommands/secondary/game_setup.py b/app/cogs/slashcommands/secondary/game_create.py similarity index 87% rename from app/cogs/slashcommands/secondary/game_setup.py rename to app/cogs/slashcommands/secondary/game_create.py index 52038bd..ced3459 100644 --- a/app/cogs/slashcommands/secondary/game_setup.py +++ b/app/cogs/slashcommands/secondary/game_create.py @@ -14,7 +14,7 @@ from bot import configFile, yaml_load, yaml_dump, cogsDir, unloadCog, dataFile, #### Game Role and Channel Setup Command -class GameSetup(commands.Cog, name='Game Setup'): +class GameCreate(commands.Cog, name='Game Create'): def __init__(self, client): self.client = client @@ -24,12 +24,10 @@ class GameSetup(commands.Cog, name='Game Setup'): 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)) + 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)) + 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', @@ -115,31 +113,29 @@ class GameSetup(commands.Cog, name='Game Setup'): 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.```',hidden=True) + 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'```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) + 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'```The minimum number of players cannot exceed the maximum number of players.```',hidden=True) + 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'```The number of reserved spaces cannot exceed the maximum number of players.```',hidden=True) + 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'```You cannot enter negative integers for the number of players.```',hidden=True) + 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'```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) + 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] = {} + 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: @@ -260,7 +256,7 @@ class GameSetup(commands.Cog, name='Game Setup'): 'gm': gm.id, 'max_players': max_players, 'min_players': min_players, - 'current_players': current_players, + 'current_players': current_players if current_players is not None else 0, 'system': system, 'platform': platform, 'role': r.id, @@ -294,7 +290,10 @@ class GameSetup(commands.Cog, name='Game Setup'): if self.client.get_cog('Player Commands') is None: loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py') flag = True + if self.client.get_cog('Pitch') 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(GameSetup(client)) \ No newline at end of file + client.add_cog(GameCreate(client)) \ No newline at end of file diff --git a/app/cogs/slashcommands/secondary/game_management.py b/app/cogs/slashcommands/secondary/game_management.py index 6cdb900..8fe2028 100644 --- a/app/cogs/slashcommands/secondary/game_management.py +++ b/app/cogs/slashcommands/secondary/game_management.py @@ -52,8 +52,14 @@ class GameManagement(commands.Cog, name='Game Management'): categories = yaml_load(categoriesFile) guildStr = str(ctx.guild.id) rStr = str(game_role.id) + 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 rStr 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.```', hidden=True) + await ctx.send(f'```Error: This is not a valid game role. Please mention a role that is associated with a game.```', hidden=True) + return + if ctx.channel.category.id != lookup[guildStr][rStr]['category']: + await ctx.send(f'```Error: You must issue this command in a text channel associated with the game you are trying to delete.```', hidden=True) return game_title = lookup[guildStr][rStr]['game_title'] time = lookup[guildStr][rStr]['time'] @@ -69,7 +75,7 @@ class GameManagement(commands.Cog, name='Game Management'): 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}`') - del categories[guildStr][c.id] + del categories[guildStr][str(c.id)] await c.delete(reason=f'/game delete command issued by `{ctx.author.display_name}`') lookup[guildStr].pop(rStr, None) gm_m = await ctx.guild.fetch_member(gm) @@ -78,11 +84,15 @@ class GameManagement(commands.Cog, name='Game Management'): 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}```',hidden=True) + if 'mod' in conf[guildStr]['channels'] and 'committee' in conf[guildStr]['roles']: + c = discord.utils.find(lambda x: x.id == conf[guildStr]['channels']['mod'], ctx.guild.channels) + await c.send( + content = 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)]: - del gms[guildStr][str(gm)] + if not gms[guildStr][str(gm)]: del gms[guildStr][str(gm)] + if not data[guildStr][time]: del data[guildStr][time] yaml_dump(lookup, lookupFile) yaml_dump(data, dataFile) yaml_dump(gms, gmFile) @@ -90,6 +100,7 @@ class GameManagement(commands.Cog, name='Game Management'): if not any([x for x in yaml_load(lookupFile).values()]): unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') if self.client.get_cog('Player Commands') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py') + if self.client.get_cog('Pitch') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py') await self.client.slash.sync_all_commands() @cog_ext.cog_subcommand( @@ -174,7 +185,7 @@ class GameManagement(commands.Cog, name='Game Management'): ): await ctx.channel.trigger_typing() if all(x is None for x in [timeslot, gm, max_players, game_title, min_players, current_players, system, platform]): - await ctx.send(f'```No parameters have been entered to modify the game.```',hidden=True) + await ctx.send(f'```Error: No parameters have been entered to modify the game.```',hidden=True) return conf = yaml_load(configFile) data = yaml_load(dataFile) @@ -194,20 +205,20 @@ class GameManagement(commands.Cog, name='Game Management'): data[guildStr][time] = {} # Command Validation Checks if rStr 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.```',hidden=True) + await ctx.send(f'```Error: This is not a valid game role. Please mention a role that is associated with a game.```',hidden=True) return if timeslot is not None: 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.```',hidden=True) + 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 any(x is not None and x < 0 for x in [min_players, max_players, current_players]): - await ctx.send(f'```You cannot enter negative integers for the number of players.```',hidden=True) + await ctx.send(f'```Error: You cannot enter negative integers for the number of players.```',hidden=True) return if min_players and max_players and min_players > max_players: - await ctx.send(f'```The minimum number of players cannot exceed the maximum number of players.```',hidden=True) + await ctx.send(f'```Error: The minimum number of players cannot exceed the maximum number of players.```',hidden=True) return if current_players and max_players and current_players > max_players: - await ctx.send(f'```The number of reserved spaces cannot exceed the maximum number of players.```',hidden=True) + await ctx.send(f'```Error: The number of reserved spaces cannot exceed the maximum number of players.```',hidden=True) return # Infer Old Data @@ -223,7 +234,7 @@ class GameManagement(commands.Cog, name='Game Management'): del data[guildStr][old_time] if game_title and game_title != old_data['game_title']: 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'```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.```',hidden=True) + await ctx.send(f'```Error: 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.```',hidden=True) return result = ''.join([result,f"The game's title has been updated to {game_title}\n"]) game_title = old_data['game_title'] if not game_title else game_title @@ -233,6 +244,7 @@ class GameManagement(commands.Cog, name='Game Management'): # Update Role await r.edit( mentionable=True, + name=f'{time.upper()}: {game_title}', permissions=discord.Permissions.none(), reason=f'/game modify command issued by `{ctx.author.display_name}`', colour=discord.Colour.green() @@ -288,6 +300,7 @@ class GameManagement(commands.Cog, name='Game Management'): else: cExists= True await c.edit( + name=f'{time.upper()}: {game_title}', overwrites=permissions, reason=f'/game modify command issued by `{ctx.author.display_name}`', ) @@ -416,10 +429,10 @@ class GameManagement(commands.Cog, name='Game Management'): del lookup[guildStr][str(r.id)] del data[guildStr][timeslot] - if 'timeslots' not in conf[guildStr]: - conf[guildStr]['timeslots'] = {} - tsDict = {k: conf[guildStr]['timeslots'][k] for k in conf[guildStr]['timeslots'] if data[guildStr][k]} - optionsList = [create_select_option(label=tsDict[x], value=x, description=x) for x in tsDict].insert(0, create_select_option(label='All Timeslots', value='--all', description='--all')) + if 'timeslots' not in conf[guildStr]: conf[guildStr]['timeslots'] = {} + tsDict = {k: conf[guildStr]['timeslots'][k] for k in data[guildStr] if data[guildStr][k]} + optionsList = [create_select_option(label=tsDict[x], value=x, description=x) for x in tsDict] + optionsList.insert(0, create_select_option(label='All Timeslots', value='--all', description='--all')) try: m = await ctx.send( content='```Select which time slot for which you would like to purge all games.```', @@ -448,7 +461,7 @@ class GameManagement(commands.Cog, name='Game Management'): return if timeslot == '--all': m = await ctx.send( - content=f'```You are attempting to purge games for all channels. This will delete every game currently running for guild {ctx.guild.name}. Are you sure?```', + content=f'```You are attempting to purge games for all time slots. This will delete every game currently running for guild {ctx.guild.name}. Are you sure?```', delete_after=5, components=[ create_actionrow( @@ -474,23 +487,68 @@ class GameManagement(commands.Cog, name='Game Management'): else: break await m.delete() - if select_ctx.custom_id == 'purge_no': + if button_ctx.custom_id == 'purge_no': await ctx.send(f'```The action `/game purge --all` has been aborted.```',hidden=True) return await ctx.channel.trigger_typing() - for t in data[guildStr]: - await purgeGames(ctx=ctx, timeslot=t) - await ctx.send( - content = '```All games for all time slots have been purged.```', - hidden=True - ) + ctx_id = ctx.channel.id + for t in list(data[guildStr]): await purgeGames(ctx=ctx, timeslot=t) + if discord.utils.find(lambda x: x.id == ctx_id, ctx.guild.text_channels) is not None: + await ctx.send( + content = '```All games for all time slots have been purged.```', + hidden=True + ) + else: + if 'mod' in conf[guildStr]['channels'] and 'committee' in conf[guildStr]['roles']: + c = discord.utils.find(lambda x: x.id == conf[guildStr]['channels']['mod'], ctx.guild.channels) + await c.send( + content = '```All games for all time slots have been purged.```' + ) else: - await ctx.channel.trigger_typing() - await purgeGames(ctx=ctx, timeslot=timeslot) - await ctx.send( - content = f'```All games for time slot `{conf[guildStr]["timeslots"][timeslot]}` have been purged.```', - hidden=True + m = await ctx.send( + content=f'```You are attempting to purge games for `{conf[guildStr]["timeslots"][timeslot]}` for guild {ctx.guild.name}. Are you sure?```', + delete_after=5, + components=[ + create_actionrow( + create_button( + style=ButtonStyle.green, + label='Yes', + emoji='👍', + custom_id='purge_yes', + ), + create_button( + style=ButtonStyle.red, + label='No', + emoji='👎', + custom_id='purge_no', + ) + ) + ] ) + while True: + button_ctx = await wait_for_component(self.client, messages=m, timeout=5) + if button_ctx.author != ctx.author: + await button_ctx.send(f'```Invalid response: you are not the person who issued the command.```', hidden=True) + else: + break + await m.delete() + if button_ctx.custom_id == 'purge_no': + await ctx.send(f'```The action `/game purge {timeslot}` has been aborted.```',hidden=True) + return + await ctx.channel.trigger_typing() + ctx_id = ctx.channel.id + await purgeGames(ctx=ctx, timeslot=timeslot) + if discord.utils.find(lambda x: x.id == ctx_id, ctx.guild.text_channels) is not None: + await ctx.send( + content = f'```All games for time slot `{conf[guildStr]["timeslots"][timeslot]}` have been purged.```', + hidden=True + ) + else: + if 'mod' in conf[guildStr]['channels'] and 'committee' in conf[guildStr]['roles']: + c = discord.utils.find(lambda x: x.id == conf[guildStr]['channels']['mod'], ctx.guild.channels) + await c.send( + content = f'```All games for time slot `{conf[guildStr]["timeslots"][timeslot]}` have been purged.```' + ) yaml_dump(gms,gmFile) yaml_dump(lookup,lookupFile) yaml_dump(data,dataFile) @@ -498,8 +556,8 @@ class GameManagement(commands.Cog, name='Game Management'): if not any([x for x in yaml_load(lookupFile).values()]): unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') if self.client.get_cog('Player Commands') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py') + if self.client.get_cog('Pitch') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py') await self.client.slash.sync_all_commands() - def setup(client): client.add_cog(GameManagement(client)) \ No newline at end of file diff --git a/app/cogs/slashcommands/secondary/manipulate_timeslots.py b/app/cogs/slashcommands/secondary/manipulate_timeslots.py index 547b880..02339d9 100644 --- a/app/cogs/slashcommands/secondary/manipulate_timeslots.py +++ b/app/cogs/slashcommands/secondary/manipulate_timeslots.py @@ -93,8 +93,12 @@ class ManipulateTimeslots(commands.Cog, name='Manipulate Timeslots'): unloadCog(f'./{cogsDir}/slashcommands/secondary/manipulate_timeslots.py') if self.client.get_cog('Game Management') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') - if self.client.get_cog('Game Setup') is not None: - unloadCog(f'./{cogsDir}/slashcommands/secondary/game_setup.py') + if self.client.get_cog('Game Create') is not None: + unloadCog(f'./{cogsDir}/slashcommands/secondary/game_create.py') + if self.client.get_cog('Player Commands') is not None: + unloadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py') + if self.client.get_cog('Pitch') is not None: + unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py') await self.client.slash.sync_all_commands() else: await ctx.send('```Error: You cannot delete a timeslot that has existing game entries. Please delete all games first.```',hidden=True) diff --git a/app/cogs/slashcommands/secondary/pitch.py b/app/cogs/slashcommands/secondary/pitch.py new file mode 100644 index 0000000..97915ab --- /dev/null +++ b/app/cogs/slashcommands/secondary/pitch.py @@ -0,0 +1,288 @@ +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, ButtonStyle +from discord_slash.client import SlashCommand +from discord_slash.utils.manage_components import create_select, create_select_option, create_actionrow, wait_for_component, create_button, create_actionrow + +from bot import configFile, yaml_load, yaml_dump, cogsDir, unloadCog, dataFile, lookupFile, gmFile, categoriesFile + +#### Pitch Command + +class Pitch(commands.Cog, name='Pitch Command'): + 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_slash( + name='pitch', + description='Designates the various key roles referenced by the Bot.', + default_permission=False, + permissions=permissions, + guild_ids=guild_ids, + ) + async def _pitch(self, ctx:SlashContext): + await ctx.channel.trigger_typing() + conf = yaml_load(configFile) + data = yaml_load(dataFile) + lookup = yaml_load(lookupFile) + gms = yaml_load(gmFile) + categories = yaml_load(categoriesFile) + pitches = {} + guildStr = str(ctx.guild.id) + if 'timeslots' not in conf[guildStr]: conf[guildStr]['timeslots'] = {} + tsDict = {k: conf[guildStr]['timeslots'][k] for k in data[guildStr] if data[guildStr][k]} + optionsList = [create_select_option(label=tsDict[x], value=x, description=x) for x in tsDict] + try: + m = await ctx.send( + content='```Select which time slot for which you would like to run pitches for.```', + delete_after=5, + components=[ + create_actionrow( + create_select( + placeholder='Time Slot', + options= optionsList, + min_values=1, + max_values=1 + ) + ) + ] + ) + while True: + select_ctx = await wait_for_component(self.client,messages=m, timeout=5) + if select_ctx.author != ctx.author: + await select_ctx.send(f'```Invalid response: you are not the person who issued the command.```', hidden=True) + else: + break + await m.delete() + [timeslot] = select_ctx.selected_options + except asyncio.TimeoutError: + await ctx.send(f'```Error: Command timed out.```', hidden=True) + return + await ctx.channel.trigger_typing() + p = discord.PermissionOverwrite() + p.read_messages = False + p.send_messages = False + await ctx.channel.edit( + reason=f'/pitch command issued by {ctx.author.display_name}', + overwrites = { + ctx.guild.default_role: p + } + ) + if guildStr not in pitches: pitches[guildStr] = {} + if timeslot not in pitches[guildStr]: pitches[guildStr][timeslot] = {} + pitches[guildStr][timeslot]['entries'] = [x for x in data[guildStr][timeslot].values()] + pitches[guildStr][timeslot]['entries'].sort(key= lambda x: x['game_title']) + header_message = await ctx.channel.send( + content=f'**Game listing for {conf[guildStr]["timeslots"][timeslot]}**\n_ _```The following are the games that are being pitched. Please select which game you would like to join by clicking on the `Join` button below.```' + ) + pitches[guildStr][timeslot]['messages'] = [] + pitches[guildStr][timeslot]['roles'] = {} + for index, element in enumerate(pitches[guildStr][timeslot]['entries']): + gm = await self.client.fetch_user(element["gm"]) + o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n' + if element['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n']) + if element['min_players'] is not None: o = ''.join([o,f'Minimum Players: {str(element["min_players"])} ']) + if element['max_players'] is not None: o = ''.join([o,f'Maximum Players: {str(element["max_players"])}\n']) + if element['platform'] is not None: o = ''.join([o,f'Platform: {element["platform"]}\n']) + o = ''.join([o,f'```']) + spaces_remaining = element["max_players"] - element["current_players"] + o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}']) + m = await ctx.channel.send( + content=o, + components=[ + create_actionrow( + create_button( + style=ButtonStyle.green, + label='Join', + emoji='🉑', + custom_id=f'join_{index}' + ), + create_button( + style=ButtonStyle.red, + label='Leave', + emoji='🈳', + custom_id=f'leave_{index}' + ) + ) + ] + ) + pitches[guildStr][timeslot]['messages'].append(m) + pitches[guildStr][timeslot]['roles'][index] = discord.utils.find(lambda x: x.id == element['role'],ctx.guild.roles) + newcomer = returning_player = None + if 'newcomer' in conf[guildStr]['roles']: newcomer = discord.utils.find(lambda x: x.id == conf[guildStr]['roles']['newcomer'], ctx.guild.roles) + if 'returning_player' in conf[guildStr]['roles']: returning_player = discord.utils.find(lambda x: x.id == conf[guildStr]['roles']['returning_player'], ctx.guild.roles) + buttons = [] + if returning_player is not None: + buttons.append( + create_button( + style= ButtonStyle.grey, + label= 'Allow Returning Players', + emoji= '🔁', + custom_id='allow_returning' + ) + ) + if newcomer is not None: + buttons.append( + create_button( + style= ButtonStyle.grey, + label= 'Allow Newcomers', + emoji= '🆕', + custom_id='allow_newcomers' + ) + ) + buttons.append( + create_button( + style= ButtonStyle.green, + label= 'Allow All', + emoji='🚪', + custom_id='allow_all' + ) + ) + buttons.append( + create_button( + style= ButtonStyle.red, + label= 'Close Pitches', + emoji='🔒', + custom_id='close_pitches' + ) + ) + control = await ctx.channel.send( + content='_ _\n```Control Panel:\nFor Admin Use Only```', + components=[ + create_actionrow( + *buttons + ) + ] + ) + while True: + button_ctx = await wait_for_component( + self.client, + messages=pitches[guildStr][timeslot]['messages'] + [control] + ) + if button_ctx.origin_message.id == control.id: + if not (set(ctx.author.roles) & set([ctx.guild.get_role(x) for x in conf[str(ctx.guild.id)]['roles']['admin']]) or ctx.author == ctx.guild.owner): + await button_ctx.send(f'```Error: You are not authorised to do this. The control panel may only be issued by an administrator.```',hidden=True) + else: + if button_ctx.custom_id == 'allow_returning': + await ctx.channel.set_permissions(reason=f'/pitch command issued by {ctx.author.display_name}', target=returning_player, read_messages=True) + await button_ctx.send(f'```Returning Players have now been allowed access to the pitch menu.```', hidden=True) + if button_ctx.custom_id == 'allow_newcomers': + ctx.channel.set_permissions(reason=f'/pitch command issued by {ctx.author.display_name}', target=newcomer, read_messages=True) + await button_ctx.send(f'```Newcomers have now been allowed access to the pitch menu.```', hidden=True) + if button_ctx.custom_id == 'allow_all': + await ctx.channel.edit(reason=f'/pitch command issued by {ctx.author.display_name}', target=ctx.guild.default_role, read_messages= True, send_messages=False) + await button_ctx.send(f'```All members have now been allowed access to the pitch menu.```', hidden=True) + if button_ctx.custom_id == 'close_pitches': break + else: + index = int(button_ctx.custom_id.split('_',1)[1]) + if button_ctx.custom_id.startswith('join_'): + if set(button_ctx.author.roles) & set(pitches[guildStr][timeslot]['roles']): + for role in list(set(button_ctx.author.roles) & set(pitches[guildStr][timeslot]['roles'])): + if role != pitches[guildStr][timeslot]['roles'][index]: + await button_ctx.author.remove_roles(role,reason=f'/pitch interaction by {button_ctx.author.display_name}') + data[guildStr][timeslot][str(role.id)]['current_players'] -= 1 + element = pitches[guildStr][timeslot]['entries'][index] + gm = await self.client.fetch_user(element['gm']) + o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n' + if element['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n']) + if element['min_players'] is not None: o = ''.join([o,f'Minimum Players: {str(element["min_players"])} ']) + if element['max_players'] is not None: o = ''.join([o,f'Maximum Players: {str(element["max_players"])}\n']) + if element['platform'] is not None: o = ''.join([o,f'Platform: {element["platform"]}\n']) + o = ''.join([o,f'```']) + spaces_remaining = element["max_players"] - element["current_players"] + o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}']) + await pitches[guildStr][timeslot]['messages'][index].edit(content=o) + tc = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['text_channel'],ctx.guild.text_channels) + if tc is None: + c = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['category'],ctx.guild.categories) + if c is not None: + tPos = len(ctx.guild.channels) + for t in c.text_channels: + if t.position <= tPos: + tc = t + tPos = t.position + if tc is not None: + await tc.send(f'```{button_ctx.author.display_name} has left the game.```') + role = pitches[guildStr][timeslot]['roles'][index] + if role in button_ctx.author.roles: + await button_ctx.send(f'```Error: You are already in the game `{lookup[guildStr][str(role.id)]["game_title"]}`.```', hidden=True) + else: + await button_ctx.author.add_roles(role,reason=f'/pitch interaction by {button_ctx.author.display_name}') + data[guildStr][timeslot][str(role.id)]['current_players'] += 1 + element = pitches[guildStr][timeslot]['entries'][index] + gm = await self.client.fetch_user(element['gm']) + o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n' + if element['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n']) + if element['min_players'] is not None: o = ''.join([o,f'Minimum Players: {str(element["min_players"])} ']) + if element['max_players'] is not None: o = ''.join([o,f'Maximum Players: {str(element["max_players"])}\n']) + if element['platform'] is not None: o = ''.join([o,f'Platform: {element["platform"]}\n']) + o = ''.join([o,f'```']) + spaces_remaining = element["max_players"] - element["current_players"] + o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}']) + await pitches[guildStr][timeslot]['messages'][index].edit(content=o) + await button_ctx.send(f'You have joined the game `{lookup[guildStr][str(role.id)]["game_title"]}`.',hidden=True) + tc = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['text_channel'],ctx.guild.text_channels) + if tc is None: + c = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['category'],ctx.guild.categories) + if c is not None: + tPos = len(ctx.guild.channels) + for t in c.text_channels: + if t.position <= tPos: + tc = t + tPos = t.position + if tc is not None: + await tc.send(f'```{button_ctx.author.display_name} has joined the game.```') + elif button_ctx.custom_id.startswith('leave_'): + role = pitches[guildStr][timeslot]['roles'][index] + if role not in button_ctx.author.roles: + await button_ctx.send(f'```Error: You are not in the game `{lookup[guildStr][str(role.id)]["game_title"]}`.```', hidden=True) + else: + await button_ctx.author.remove_roles(role,reason=f'/pitch interaction by {button_ctx.author.display_name}') + data[guildStr][timeslot][str(role.id)]['current_players'] -= 1 + element = pitches[guildStr][timeslot]['entries'][index] + gm = await self.client.fetch_user(element['gm']) + o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n' + if element['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n']) + if element['min_players'] is not None: o = ''.join([o,f'Minimum Players: {str(element["min_players"])} ']) + if element['max_players'] is not None: o = ''.join([o,f'Maximum Players: {str(element["max_players"])}\n']) + if element['platform'] is not None: o = ''.join([o,f'Platform: {element["platform"]}\n']) + o = ''.join([o,f'```']) + spaces_remaining = element["max_players"] - element["current_players"] + o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}']) + await pitches[guildStr][timeslot]['messages'][index].edit(content=o) + await button_ctx.send(f'You have left the game `{lookup[guildStr][str(role.id)]["game_title"]}`.',hidden=True) + tc = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['text_channel'],ctx.guild.text_channels) + if tc is None: + c = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['category'],ctx.guild.categories) + if c is not None: + tPos = len(ctx.guild.channels) + for t in c.text_channels: + if t.position <= tPos: + tc = t + tPos = t.position + if tc is not None: + await tc.send(f'```{button_ctx.author.display_name} has left the game.```') + yaml_dump(data, dataFile) + await header_message.delete() + for message in pitches[guildStr][timeslot]['messages']: await message.delete() + await control.delete() + await ctx.channel.edit(reason=f'/pitch command issued by {ctx.author.display_name}', overwrites={}) + await button_ctx.send('```Pitch menu cleared. Pitches have now been concluded.```') + +def setup(client): + client.add_cog(Pitch(client)) \ No newline at end of file diff --git a/app/cogs/slashcommands/secondary/player_commands.py b/app/cogs/slashcommands/secondary/player_commands.py index 6791210..c3cfad4 100644 --- a/app/cogs/slashcommands/secondary/player_commands.py +++ b/app/cogs/slashcommands/secondary/player_commands.py @@ -9,7 +9,8 @@ from discord_slash.model import SlashCommandPermissionType from bot import configFile, yaml_load, yaml_dump, lookupFile, dataFile, gmFile, categoriesFile -##### Game Management Commands +##### Player Add, Remove, and Leave Commands + class PlayerCommands(commands.Cog, name='Player Commands'): def __init__(self, client): self.client = client @@ -81,9 +82,9 @@ class PlayerCommands(commands.Cog, name='Player Commands'): header = discord.utils.find(lambda x: x.id == hm, p) if header is not None: text = header.content.split('\n') - for line in text: - if 'Current Players: ' in line: - line = f'Current Players: {str(data[guildStr][t][rStr]["current_players"])}' + for line, item in enumerate(text): + if 'Current Players: ' in item: + text[line] = f'Current Players: {str(data[guildStr][t][rStr]["current_players"])}' break await header.edit(content='\n'.join(text)) else: @@ -135,9 +136,6 @@ class PlayerCommands(commands.Cog, name='Player Commands'): return game = discord.utils.find(lambda x: x.id == categories[guildStr][str(ctx.channel.category.id)], ctx.guild.roles) rStr = str(game.id) - if game not in player.roles: - await ctx.send(f'```Error: Player `{player.display_name}` is not in the game {lookup[guildStr][rStr]["game_title"]}.```',hidden=True) - return if not (set(ctx.author.roles) & set([ctx.guild.get_role(x) for x in conf[str(ctx.guild.id)]['roles']['admin']]) or ctx.author == ctx.guild.owner): if not set(ctx.author.roles) & set([ctx.guild.get_role(int(x)) for x in gms[str(ctx.guild.id)] if gms[str(ctx.guild.id)][x]]): await ctx.send(f'```Error: You are not authorised to issue this command. The command may only be issued by an administrator or by a GM.```',hidden=True) @@ -145,6 +143,9 @@ class PlayerCommands(commands.Cog, name='Player Commands'): if ctx.author.id != lookup[guildStr][rStr]['gm']: await ctx.send(f'```Error: You are not authorised to issue this command. A player may only be added to a game by the GM or by an administrator.```',hidden=True) return + if game not in player.roles: + await ctx.send(f'```Error: Player `{player.display_name}` is not in the game {lookup[guildStr][rStr]["game_title"]}.```',hidden=True) + return await player.remove_roles(game, reason=f'`/player remove` command issued by {ctx.author.display_name}`.') t = lookup[guildStr][rStr]['time'] if data[guildStr][t][rStr]['current_players'] <= 1: data[guildStr][t][rStr]['current_players'] = None @@ -157,9 +158,9 @@ class PlayerCommands(commands.Cog, name='Player Commands'): header = discord.utils.find(lambda x: x.id == hm, p) if header is not None: text = header.content.split('\n') - for line in text: - if 'Current Players: ' in line: - line = f'Current Players: {str(data[guildStr][t][rStr]["current_players"]) if data[guildStr][t][rStr]["current_players"] is not None else str(0)}' + for line, item in enumerate(text): + if 'Current Players: ' in item: + text[line] = f'Current Players: {str(data[guildStr][t][rStr]["current_players"]) if data[guildStr][t][rStr]["current_players"] is not None else str(0)}' break await header.edit(content='\n'.join(text)) else: @@ -198,12 +199,12 @@ class PlayerCommands(commands.Cog, name='Player Commands'): guildStr = str(ctx.guild.id) player = ctx.author if str(ctx.channel.category.id) not in categories[guildStr]: - await ctx.send(f'```Error: This command can only be issued in a text channel associated with a game.```', hidden=True) + await ctx.send(f'```Error: This command can only be issued in a text channel associated with the game you are trying to leave.```', hidden=True) return - game = categories[guildStr][str(ctx.channel.category.id)] + game = discord.utils.find(lambda x: x.id == categories[guildStr][str(ctx.channel.category.id)], ctx.guild.roles) rStr = str(game.id) if game not in player.roles: - await ctx.send(f'```Error: Player `{player.display_name}` is not in the game {lookup[guildStr][rStr]["game_title"]}.```',hidden=True) + await ctx.send(f'```Error: You are not in the game {lookup[guildStr][rStr]["game_title"]}.```',hidden=True) return await player.remove_roles(game, reason=f'`/player leave` command issued by {ctx.author.display_name}`.') t = lookup[guildStr][rStr]['time'] @@ -217,9 +218,9 @@ class PlayerCommands(commands.Cog, name='Player Commands'): header = discord.utils.find(lambda x: x.id == hm, p) if header is not None: text = header.content.split('\n') - for line in text: - if 'Current Players: ' in line: - line = f'Current Players: {str(data[guildStr][t][rStr]["current_players"]) if data[guildStr][t][rStr]["current_players"] is not None else str(0)}' + for line, item in enumerate(text): + if 'Current Players: ' in item: + text[line] = f'Current Players: {str(data[guildStr][t][rStr]["current_players"]) if data[guildStr][t][rStr]["current_players"] is not None else str(0)}' break await header.edit(content='\n'.join(text)) else: @@ -229,7 +230,7 @@ class PlayerCommands(commands.Cog, name='Player Commands'): if t.position <= tPos: tc = t tPos = t.position - await tc.send(f'```Player {player.display_name} has left the game.```\n{game.mention}') + await tc.send(f'```Player `{player.display_name}` has left the game.```\n{game.mention}') if 'mod' in conf[guildStr]['channels'] and 'committee' in conf[guildStr]['roles']: c = discord.utils.find(lambda x: x.id == conf[guildStr]['channels']['mod'], ctx.guild.channels) r = discord.utils.find(lambda x: x.id == conf[guildStr]['roles']['committee'], ctx.guild.roles) diff --git a/app/data/categories.yml b/app/data/categories.yml index fccd3f5..c9281f9 100644 --- a/app/data/categories.yml +++ b/app/data/categories.yml @@ -1,3 +1,2 @@ '864651943820525609': - '866788661026488352': 866788659839238164 - '867516972496060446': 867516971017175070 + '867789192527478800': 867789191156203550 diff --git a/app/data/config.yml b/app/data/config.yml index 113d868..1583547 100644 --- a/app/data/config.yml +++ b/app/data/config.yml @@ -21,4 +21,5 @@ returning_player: 866645365524660224 student: 866645394699714570 timeslots: - suneve: Sunday Evening + avatar: Avatar Time + shera: She Ra Time diff --git a/app/data/data.yml b/app/data/data.yml index cf18a9c..e7c246d 100644 --- a/app/data/data.yml +++ b/app/data/data.yml @@ -1,26 +1,14 @@ '864651943820525609': - suneve: - '866788659839238164': - category: 866788661026488352 - current_players: null - game_title: Avatar Legends - gm: 493694762210033664 - header_message: 866825474252210196 - max_players: 6 - min_players: null - platform: null - role: 866788659839238164 - system: null - text_channel: 866788663194812446 - '867516971017175070': - category: 867516972496060446 - current_players: 1 + avatar: + '867789191156203550': + category: 867789192527478800 + current_players: 0 game_title: Masks - gm: 493694762210033664 - header_message: 867517905620303894 + gm: 864649599671205914 + header_message: 867789196534349826 max_players: 5 min_players: null platform: null - role: 867516971017175070 + role: 867789191156203550 system: null - text_channel: 867516974212055060 + text_channel: 867789194784407552 diff --git a/app/data/gm.yml b/app/data/gm.yml index a48a7e8..923c2c7 100644 --- a/app/data/gm.yml +++ b/app/data/gm.yml @@ -1,5 +1,3 @@ '864651943820525609': - '493694762210033664': - - 866788659839238164 - - 867516971017175070 - '864649599671205914': [] + '864649599671205914': + - 867789191156203550 diff --git a/app/data/lookup.yml b/app/data/lookup.yml index e09304b..354dcc9 100644 --- a/app/data/lookup.yml +++ b/app/data/lookup.yml @@ -1,13 +1,7 @@ '864651943820525609': - '866788659839238164': - category: 866788661026488352 - game_title: Avatar Legends - gm: 493694762210033664 - text_channel: 866788663194812446 - time: suneve - '867516971017175070': - category: 867516972496060446 + '867789191156203550': + category: 867789192527478800 game_title: Masks - gm: 493694762210033664 - text_channel: 867516974212055060 - time: suneve + gm: 864649599671205914 + text_channel: 867789194784407552 + time: avatar