Started writing /commands.

Completed channel group of config subcommands
Fixed bugs in config initialisation function
This commit is contained in:
Vivek Santayana 2021-07-17 13:56:04 +01:00
parent 51a01bab49
commit 78bc73c023
11 changed files with 259 additions and 41 deletions

26
TODO.md
View File

@ -5,17 +5,21 @@
- [x] Split event listeners into individual cogs.
- [x] Update with re-organised data and config structure
> - [x] Correct references to data in existing cogs.
- [x] Setup minimally functioning configs of guild on startup
- [x] Synchronise core configuration `/commands` on startup
- [ ] Synchronise secondary `/commands` on complete configuration **(see below)**
## Bot Functionality
## Bot Functionality and Processes
- [ ] 'Delete Commands' Function
- [ ] 'Register Commands' Function
- [ ] Infer Permissions from Config
- [x] Infer Permissions from Config
- [x] Dynamic Command Prefixes
- [ ] Infer Games from Server Structure
- [ ] Re-enable logging
- [x] Delete Dev/Test Functions
- [x] Error handlers
- [ ] Debug Features
> - [ ] Command Installer/Uninstaller
- [x] Help Channel Event Listener
> - [x] Add Config key for Help Channel
- [ ] Slash Command Buttons or
@ -28,7 +32,7 @@
- [ ] Membership Restriction
> - [ ] Message Receive Listener
> - [ ] Membership Validation Listener
- [ ] Re-register commands after any relevant config changes
- [ ] Re-synchronise commands after any relevant config changes **(see above)**
- [x] Flag for checking completeness of configuration for a guild.
> - [x] Function for checking configs for completeness
## Event Listeners
@ -40,10 +44,24 @@
## Commands
- [ ] Configure Bot function and sub commands
> - [x] botrole (role group)
> - [ ] committeerole (role group)
> - [x] modchannel (channel group)
> - [x] help channel (channel group)
> - [x] signup channel (channel group)
> - [ ] newcomer role (role group)
> - [ ] returning player role (role group)
> - [ ] student role (role group)
> - [ ] help notifications (notification group)
> - [ ] signup notifications (notification group)
- [ ] Set up command permissions
> - [ ] Slash Commands
>> - [x] Admin Commands
>> - [ ] Game Management Commands
> - [x] Native Bot Commands
- [ ] Migrate existing bot commands
> - [ ] setupgame
> - [ ] ~~definebotrole~~ config
> - [x] ~~definebotrole~~ config
> - [ ] deletegame
> - [ ] reset
> - [ ] migrate

View File

@ -76,7 +76,7 @@ def setConfig(guild:discord.Guild):
if guildStr not in conf:
conf[guildStr] = {}
gDict = conf[guildStr]
if 'channels' not in gDict or gDict['channels'] is not dict or None in gDict['channels']:
if 'channels' not in gDict or type(gDict['channels']) is not dict or None in gDict['channels']:
gDict['channels'] = {}
cDict = gDict['channels']
if 'mod' not in cDict:
@ -89,9 +89,9 @@ def setConfig(guild:discord.Guild):
cDict['mod'] = t.id
else:
cDict['mod'] = guild.system_channel.id
if 'configured' not in gDict or gDict['configured'] is not bool:
if 'configured' not in gDict or type(gDict['configured']) is not bool:
gDict['configured'] = False
if 'membership' not in gDict or gDict['membership'] is not dict or None in gDict['membership']:
if 'membership' not in gDict or type(gDict['membership']) is not dict or None in gDict['membership']:
gDict['membership'] = {}
if 'name' not in gDict or gDict['name'] != guild.name:
gDict['name'] = guild.name
@ -99,7 +99,7 @@ def setConfig(guild:discord.Guild):
gDict['owner'] = guild.owner_id
if 'prefix' not in gDict:
gDict['prefix'] = '-'
if 'roles' not in gDict or (type(gDict)['roles'] is not dict or None in gDict['roles']):
if 'roles' not in gDict or (type(gDict['roles']) is not dict or None in gDict['roles']):
gDict['roles'] = {}
rDict = gDict['roles']
if 'admin' not in rDict or (type(rDict['admin']) is not list or len(rDict['admin']) == 0 or None in rDict['admin']):
@ -107,7 +107,7 @@ def setConfig(guild:discord.Guild):
for role in guild.roles:
if not (role.is_bot_managed() or role.is_integration()) and role.permissions.administrator:
rDict['admin'].append(role.id)
if 'timeslots' not in gDict or gDict['timeslots'] is not dict or None in gDict['timeslots']:
if 'timeslots' not in gDict or type(gDict['timeslots']) is not dict or None in gDict['timeslots']:
gDict['timeslots'] = []
yaml_dump(conf, configFile)
@ -133,7 +133,8 @@ def checkConfig(guild:discord.Guild):
for item in list(i.split("'")):
if '[' in item or ']' in item:
s.remove(item)
unconfigured.append('.'.join(s))
if 'notifications' not in '.'.join(s):
unconfigured.append('.'.join(s))
if 'type_changes' in diff:
for i in diff['type_changes']:
s = i.split("'")
@ -155,6 +156,8 @@ def checkConfig(guild:discord.Guild):
unconfigured.append(i)
if 'meta' in unconfigured:
unconfigured.remove('meta')
if 'initialised' in unconfigured:
unconfigured.remove('initialised')
if 'configured' in unconfigured:
unconfigured.remove('configured')
output = list(set(unconfigured))
@ -228,8 +231,9 @@ def unloadCogs(cogClass:str = 'all'):
if cogfile.endswith('.py'):
unloadCog(f'./{cogsDir}/{category}/{cogfile}')
loadCogs('dev')
loadCogs('devcommands')
loadCogs('events')
loadCogs('botcommands')
loadCogs('slashcommands')
client.run(os.getenv('TEST_3_TOKEN'))

View File

@ -9,7 +9,14 @@ from bot import configFile, yaml_load, yaml_dump
class Prefix(commands.Cog, name='Server Command Prefix'):
def __init__(self, client):
self.client = client
#### Check if user is an administrator
async def cog_check(self, ctx):
for role in ctx.author.roles:
if role.permissions.administrator:
return True
return ctx.author.guild_permissions.administrator
@commands.command(
name = 'changeprefix',
aliases = ['prefix'],
@ -20,7 +27,7 @@ class Prefix(commands.Cog, name='Server Command Prefix'):
conf = yaml_load(configFile)
conf[str(ctx.guild.id)]['prefix'] = prefix.lower()
yaml_dump(conf, configFile)
await ctx.send(f"`{self.client.user.name}`'s prefix for native bot commands has been changed to `{prefix}` for the guild `{ctx.guild.name}`.\n`Note: This will not affect /commands.`")
await ctx.send(f"```{self.client.user.name}'s prefix for native bot commands has been changed to `{prefix}` for the guild `{ctx.guild.name}`.\n\nNote: This will not affect /commands.```")
def setup(client):
client.add_cog(Prefix(client))

View File

@ -16,6 +16,13 @@ class Dev(commands.Cog, name='Developer Commands'):
def __init__(self, client):
self.client = client
#### Check if user is an administrator
async def cog_check(self, ctx):
for role in ctx.author.roles:
if role.permissions.administrator:
return True
return ctx.author.guild_permissions.administrator
@commands.command(
name='debug',
description='Toggles debug feature for the guild. Enter either `on` or `off`.',

View File

@ -16,7 +16,10 @@ class on_command_error(commands.Cog, name='On Command Error'):
@commands.Cog.listener()
async def on_command_error(self, ctx, error):
if isinstance(error, discord.DiscordException):
if isinstance(error, commands.CheckFailure):
if isinstance(error, commands.CommandNotFound):
print(f'Error: User {ctx.author.name}#{ctx.author.discriminator} / {ctx.author.display_name} entered an invalid command <{ctx.message.clean_content}> in the guild {ctx.guild.name}.')
await ctx.reply(f'```Error: This is not a valid command.```')
elif isinstance(error, commands.CheckFailure):
print(f'Error: User {ctx.author.name}#{ctx.author.discriminator} / {ctx.author.display_name} is not authorised to issue the command <{ctx.command.name}> in the guild {ctx.guild.name}.')
await ctx.reply(f'```Error: You are not authorised to issue this command.```')
else:

View File

@ -7,7 +7,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
import logging
# logger and handler
from bot import checkConfig, clearConfig, configFile, parseConfigCheck, setConfig, yaml_dump, yaml_load
from bot import checkConfig, clearConfig, configFile, parseConfigCheck, setConfig, yaml_dump, yaml_load, loadCogs, unloadCogs
#### Actions for the Bot to take once it is ready to interact with commands.
class on_ready(commands.Cog, name='On Ready Events'):
@ -17,6 +17,9 @@ class on_ready(commands.Cog, name='On Ready Events'):
@commands.Cog.listener()
async def on_ready(self):
async def test(self):
await self.client.get_channel(865348933022515220).send('Foo')
#### Create any missing config entries for guilds
for guild in self.client.guilds:
setConfig(guild)
@ -32,6 +35,12 @@ class on_ready(commands.Cog, name='On Ready Events'):
conf = yaml_load(configFile)
if not status:
await guild.get_channel(conf[str(guild.id)]['channels']['mod']).send(f"```The Bot's configurations are incomplete for the guild `{guild.name}`. Some limited functions will still be available, but most features cannot be used until the configurations are complete.\n{parseConfigCheck(output)}\nYou can set these configuration values using the `/config` command.```")
# #### Reload the /commands after the configs have finished loading.
# unloadCogs('slashcommands')
# loadCogs('slashcommands')
await test(self)
def setup(client):
client.add_cog(on_ready(client))

View File

@ -0,0 +1,36 @@
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 # Slash Command features
import logging
# logger and handler
from bot import configFile, yaml_load, yaml_dump
#### Error Handler Event Listener for Slash Command Errors
class on_slash_command_error(commands.Cog, name='On Command Error'):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_slash_command_error(self, ctx:SlashContext, error):
if isinstance(error, Exception):
await ctx.send(
content='```Invalid Command: {error}```',
tts=True,
hidden=True,
delete_after=10,
)
# if isinstance(error, commands.CommandNotFound):
# print(f'Error: User {ctx.author.name}#{ctx.author.discriminator} / {ctx.author.display_name} entered an invalid command <{ctx.message.clean_content}> in the guild {ctx.guild.name}.')
# await ctx.reply(f'```Error: This is not a valid command.```')
# elif isinstance(error, commands.CheckFailure):
# print(f'Error: User {ctx.author.name}#{ctx.author.discriminator} / {ctx.author.display_name} is not authorised to issue the command <{ctx.command.name}> in the guild {ctx.guild.name}.')
# await ctx.reply(f'```Error: You are not authorised to issue this command.```')
# else:
# print(f'User {ctx.author.name}#{ctx.author.discriminator} / {ctx.author.display_name} received error: "{error}" when attempting to issue command <{ctx.command.name}> in the guild {ctx.guild.name}.')
# await ctx.reply(f'```Error: {error}```')
def setup(client):
client.add_cog(on_slash_command_error(client))

View File

@ -4,32 +4,163 @@ 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 # Slash Command features
from discord_slash.utils.manage_commands import create_choice, create_option, create_permission # Slash Command features
from discord_slash.model import SlashCommandPermissionType
from bot import configFile, yaml_load, yaml_dump
##### Configuration Cog
class Configuration(commands.Cog):
def __init__(self, client):
self.client = client
guild_ids=[int(guildKey) for guildKey in yaml_load(configFile)]
permissions = {}
conf = yaml_load(configFile)
for guildStr in conf:
permissions[int(guildStr)] = []
permissions[int(guildStr)].append(create_permission(id=conf[guildStr]['owner'],id_type=SlashCommandPermissionType.USER,permission=True))
for admin in conf[guildStr]['roles']['admin']:
permissions[int(guildStr)].append(create_permission(id=admin,id_type=SlashCommandPermissionType.ROLE,permission=True))
@cog_ext.cog_slash(
# base='botrole',
# subcommand_group='configure',
name='configure',
description='Parameter to define the role assigned to the dice bots.',
# base_description='Command to configure the various guild parameters.',
# subcommand_group_description='These are configuration commands to set up the various guild parameters.',
guild_ids=guild_ids
# options=[
# create_option(
# name='botrole',
# description='The role that the dice bots are assigned in order to access the text channels.'
# type=8,
# required=True
# )
# ]
name='hello',
description='Test command to see if registration works.',
guild_ids=guild_ids,
options=[
create_option(
name='input',
description='Choose a phrase that best goes with the opening Hello',
option_type=3,
required=True,
choices=[
create_choice(
name='...there',
value='there'
),
create_choice(
name='...world!',
value='world'
),
create_choice(
name='...beautiful!',
value='beautiful'
)
],
)
]
)
async def _configure(self, ctx:SlashContext, option):
await ctx.send(f'The `botrole` for the guild `{ctx.guild.name}` has been set to `{option}`.')
async def _hello(self, ctx:SlashContext, input):
await ctx.send(f'{input}')
@cog_ext.cog_subcommand(
base='config',
subcommand_group='role',
name='bot',
description='Designate the role on the guild which the dice bots use to access game text channels.',
base_description='Commands for configuring the various parameters of the Guild',
base_default_permission=False,
base_permissions=permissions,
subcommand_group_description='Designates the various key command roles for the guild.',
guild_ids=guild_ids,
options=[
create_option(
name='role',
description='The role assigned to dice bots to access game channels.',
option_type=8,
required=True
)
]
)
async def _config_role_bot(self, ctx:SlashContext, role:discord.Role):
conf = yaml_load(configFile)
if 'roles' not in conf[str(ctx.guild.id)]:
conf[str(ctx.guild.id)]['roles'] = {}
conf[str(ctx.guild.id)]['roles']['bots'] = int(role.id)
yaml_dump(conf, configFile)
await ctx.send(f'```The `botrole` for the guild `{ctx.guild.name}` has been set to `{role.name}`.```')
@cog_ext.cog_subcommand(
base='config',
subcommand_group='channel',
name='signup',
description='Designate the channel where members can post their sign-up confirmation.',
# base_description='Commands for configuring the various parameters of the Guild',
# base_default_permission=False,
# base_permissions=permissions,
subcommand_group_description='Designates the various key Bot channels for the guild.',
guild_ids=guild_ids,
options=[
create_option(
name='channel',
description='The channel assigned for members to verify their membership.',
option_type=7,
required=True
)
]
)
async def _config_channel_signup(self, ctx:SlashContext, channel:discord.TextChannel):
conf = yaml_load(configFile)
if 'channels' not in conf[str(ctx.guild.id)]:
conf[str(ctx.guild.id)]['channels'] = {}
conf[str(ctx.guild.id)]['channels']['signup'] = int(channel.id)
yaml_dump(conf, configFile)
await ctx.send(f'```The `signup` channel for the guild `{ctx.guild.name}` has been set to `{channel.name}`.```')
@cog_ext.cog_subcommand(
base='config',
subcommand_group='channel',
name='modlog',
description='Designate the channel for bot to post notifications for the admins.',
# base_description='Commands for configuring the various parameters of the Guild',
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Designates the various key Bot channels for the guild.',
guild_ids=guild_ids,
options=[
create_option(
name='channel',
description='The channel assigned for moderation notifications.',
option_type=7,
required=True
)
]
)
async def _config_channel_mod(self, ctx:SlashContext, channel:discord.TextChannel):
conf = yaml_load(configFile)
if 'channels' not in conf[str(ctx.guild.id)]:
conf[str(ctx.guild.id)]['channels'] = {}
conf[str(ctx.guild.id)]['channels']['mod'] = int(channel.id)
yaml_dump(conf, configFile)
await ctx.send(f'```The `moderation log` channel for the guild `{ctx.guild.name}` has been set to `{channel.name}`.```')
@cog_ext.cog_subcommand(
base='config',
subcommand_group='channel',
name='help',
description='Designate the hel channel which the bot will monitor for member queries.',
# base_description='Commands for configuring the various parameters of the Guild',
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Designates the various key Bot channels for the guild.',
guild_ids=guild_ids,
options=[
create_option(
name='channel',
description='The channel monitored by the Bot for help queries.',
option_type=7,
required=True
)
]
)
async def _config_channel_help(self, ctx:SlashContext, channel:discord.TextChannel):
conf = yaml_load(configFile)
if 'channels' not in conf[str(ctx.guild.id)]:
conf[str(ctx.guild.id)]['channels'] = {}
conf[str(ctx.guild.id)]['channels']['help'] = int(channel.id)
yaml_dump(conf, configFile)
await ctx.send(f'```The `help` channel for the guild `{ctx.guild.name}` has been set to `{channel.name}`.```')
def setup(client):
client.add_cog(Configuration(client))

View File

@ -1,6 +1,6 @@
'864651943820525609':
channels:
mod: 865348933022515220
mod: 865662560225067018
configured: false
membership: {}
name: Test
@ -12,4 +12,5 @@
roles:
admin:
- 864661232005939280
bots: 864661167297527830
timeslots: []

View File

@ -28,3 +28,4 @@ guild_id_string:
notifications:
help: true
signup: true
initialised: true

View File

@ -26,7 +26,7 @@ class Debug(commands.Cog, name='Debug Commands'):
async def _reload(self, ctx, cog_category: str='all'):
unloadCogs(cog_category)
loadCogs(cog_category)
await ctx.reply(f'`{cog_category}` cogs have been reloaded.')
await ctx.reply(f'````{cog_category}` cogs have been reloaded.```')
@commands.command(
name='unloadcogs',
@ -36,7 +36,7 @@ class Debug(commands.Cog, name='Debug Commands'):
async def _unloadcogs(self, ctx, cog_category: str='all'):
unloadCogs(cog_category)
loadCogs(cog_category)
await ctx.reply(f'`{cog_category}` cogs have been unloaded.')
await ctx.reply(f'````{cog_category}` cogs have been unloaded.```')
@commands.command(
name='loadcogs',
@ -46,11 +46,11 @@ class Debug(commands.Cog, name='Debug Commands'):
async def _loadcogs(self, ctx, cog_category: str='all'):
unloadCogs(cog_category)
loadCogs(cog_category)
await ctx.reply(f'`{cog_category}` cogs have been loaded.')
await ctx.reply(f'````{cog_category}` cogs have been loaded.```')
@commands.command(
name='deletecommands',
aliases=['delallcommands','deleteslashcommands'],
aliases=['delallcommands','deleteslashcommands','clearcommands','clearslashcommands'],
description='Deletes all the public and guild slash commands registered by the bot.',
brief='Delets all slash commands'
)
@ -65,7 +65,7 @@ class Debug(commands.Cog, name='Debug Commands'):
bot_token=os.getenv('TEST_3_TOKEN'),
guild_ids=[ int(g) for g in yaml_load(configFile)]
)
await ctx.reply('All slash commands have been deleted.')
await ctx.reply('```All slash commands have been deleted.```')
@commands.command(
name='retrievecommands',
@ -76,6 +76,7 @@ class Debug(commands.Cog, name='Debug Commands'):
async def _retrievecommands(self, ctx:commands.Context):
c = await utils.manage_commands.get_all_commands(self.client.user.id,os.getenv('TEST_3_TOKEN'),guild_id=ctx.guild.id)
print(c)
await ctx.reply(f'```All registered `/commands` have been fetched and sent to the Python console.```')
@commands.command(
name='clearconfig',
@ -87,7 +88,7 @@ class Debug(commands.Cog, name='Debug Commands'):
conf = yaml_load(configFile)
for key in list(conf):
clearConfig(key)
await ctx.reply(f'Config entries have been cleared.')
await ctx.reply(f'```Config entries for unknown guilds have been cleared.```')
@commands.command(
name='setconfig',
@ -97,7 +98,7 @@ class Debug(commands.Cog, name='Debug Commands'):
)
async def _setconfig(self, ctx:commands.Context):
setConfig(ctx.guild)
await ctx.reply(f'Config entry has been added for guild `{ctx.guild.name}`.')
await ctx.reply(f'```Config entry has been added for guild `{ctx.guild.name}`.```')
def setup(client):
client.add_cog(Debug(client))