Finessed game create and delete commands

This commit is contained in:
2021-07-19 15:30:04 +01:00
parent 4277ef4f91
commit 304dd9f9de
12 changed files with 489 additions and 192 deletions

View File

@ -57,7 +57,7 @@ class EditMembership(commands.Cog, name='Edit Membership'):
yaml_dump(conf, configFile)
await ctx.send(f'```Membership type {role.name} has been deleted for the guild `{ctx.guild.name}`.```')
await role.delete(reason=f'`/config membership remove` command issued by `{ctx.author.display_name}`.')
if not all([len(yaml_load(configFile)[x]['membership']) > 0 for x in yaml_load(configFile)]):
if not any([x['membership'] for x in yaml_load(configFile).values()]):
unloadCog(f'./{cogsDir}/slashcommands/secondary/edit_membership.py')
await self.client.slash.sync_all_commands()
elif len(conf[str(ctx.guild.id)]['membership']) > 0:

View File

@ -9,170 +9,19 @@ 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
from bot import configFile, yaml_load, yaml_dump, reloadCog, cogsDir, unloadCog, dataFile, lookupFile, gmFile
#### Game Role and Channel Management Commands
class GameSetup(commands.Cog, name='Game Setup'):
class GameManagement(commands.Cog, name='Game Management'):
def __init__(self, client):
self.client = client
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])))
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 GM can take.',
option_type=3,
required=True
),
create_option(
name='min_players',
description='The minimum number of players the GM can take.',
option_type=3,
required=False
),
create_option(
name='reserved_spaces',
description='The number of spaces the GM is reserving in the game.',
option_type=3,
required=False
),
create_option(
name='game_title',
description='What the game is called.',
option_type=3,
required=True
),
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:int=None,
reserved_spaces:int=None,
system:str=None,
platform:str=None
):
await ctx.channel.trigger_typing()
conf = yaml_load(configFile)
data = yaml_load(dataFile)
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.```')
return
if guildStr not in data:
data[guildStr] = {}
if time not in data[guildStr]:
data[guildStr][time] = []
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(),
)
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 = 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}`'
)
await ctx.send(f'```Roles and channels for the game `{game_title}` have been created.```\n{r.mention}')
output = f'```Hello {gm.display_name}! Yout 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'Reserved Spaces: {reserved_spaces}\n' if reserved_spaces is not None else ''])
output = ''.join([output,f'Platform: {platform}```' if platform is not None else '```'])
o = await t.send(output)
await o.pin(reason=f'/game create command issued by `{ctx.author.display_name}`')
tDict = {}
tDict['game_title'] = game_title
tDict['gm'] = gm.id
tDict['max_players'] = max_players
tDict['min_players'] = min_players
tDict['reserved_spaces'] = reserved_spaces
tDict['system'] = system
tDict['platform'] = platform
tDict['role'] = r.id
tDict['category'] = c.id
data[guildStr][time].append(tDict)
if guildStr not in lookup:
lookup[guildStr] = {}
lookup[guildStr][str(r.id)] = {}
lookup[guildStr][str(r.id)]['category'] = c.id
lookup[guildStr][str(r.id)]['gm'] = gm.id
lookup[guildStr][str(r.id)]['time'] = time
yaml_dump(data,dataFile)
yaml_dump(lookup,lookupFile)
### Move delete, Modify, and Reset commands to a separate secondary cog to enable when games exist?
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='',
@ -196,28 +45,50 @@ class GameSetup(commands.Cog, name='Game Setup'):
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..```')
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
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}`')
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()
def setup(client):
client.add_cog(GameSetup(client))
client.add_cog(GameManagement(client))

View File

@ -0,0 +1,282 @@
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 GM 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 GM 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 > max_players:
await ctx.send(f'```The minimum number of players cannot exceed the maximum number of players.```')
return
if reserved_spaces > max_players:
await ctx.send(f'```The number of reserved spaces cannot exceed the maximum 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(),
)
else:
rExists = True
await r.edit(
mentionable=True,
permissions=discord.Permissions.none(),
reason=f'/game create command issued by `{ctx.author.display_name}`',
)
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)
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 spaces {reserved_spaces} 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\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 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 = {}
gDict['game_title'] = game_title
gDict['gm'] = gm.id
gDict['max_players'] = max_players
gDict['min_players'] = min_players
gDict['current_players'] = reserved_spaces
gDict['system'] = system
gDict['platform'] = platform
gDict['role'] = r.id
gDict['category'] = c.id
gDict['text_channel'] = t.id
gDict['header_message'] = o.id
data[guildStr][time].append(gDict)
lookup[guildStr][str(r.id)] = {}
lookup[guildStr][str(r.id)]['category'] = c.id
lookup[guildStr][str(r.id)]['gm'] = gm.id
lookup[guildStr][str(r.id)]['time'] = time
lookup[guildStr][str(r.id)]['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))

View File

@ -56,8 +56,12 @@ class ManipulateTimeslots(commands.Cog, name='Manipulate Timeslots'):
await ctx.send(f'```Timeslot {conf[str(ctx.guild.id)]["timeslots"][timeslot]} with the key `{timeslot}` has been deleted for the guild `{ctx.guild.name}`.```')
conf[str(ctx.guild.id)]['timeslots'].pop(timeslot, None)
yaml_dump(conf, configFile)
if not all([len(yaml_load(configFile)[x]['timeslots']) > 0 for x in yaml_load(configFile)]):
if not all([x['timeslots'] for x in yaml_load(configFile).values()]):
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')
await self.client.slash.sync_all_commands()
elif len(conf[str(ctx.guild.id)]['timeslots']) > 0:
output = f'```Timeslot `{timeslot}` was not found in the guild `{ctx.guild.name}`. Please enter a valid key.\n\n Available timeslots are:\n(key): (timeslot name)'