Added config checking, event listeners, etc.

Can track added features by comparing TODO list.
This commit is contained in:
Vivek Santayana 2021-07-16 23:53:31 +01:00
parent 1d355e3b2d
commit 51a01bab49
22 changed files with 346 additions and 77 deletions

1
.gitignore vendored
View File

@ -146,6 +146,7 @@ pyvenv.cfg
app/cogs/template.py.tmp
app/cogs/events.py.tmp
app/old_code/
app/run_venv.sh
# ---> VisualStudioCode
.vscode/*

View File

@ -9,7 +9,7 @@ I cannot believe how much I got into the debate between these two formats.
Seriously, the database was overkill and such a bad idea.
It was fun to learn but was such a bad idea.`
- Implements client-side `/commands` as the core method of interaction
`This takes advantage of a new Discord feature to programme `/commands` into the console via Bot integration rather than natively at the bot.
`This takes advantage of a new Discord feature to programme /commands into the console via Bot integration rather than natively at the bot.
This offers more robust command handling, with client-side data validation, option entries, input menus, etc.
This does have the drawback of requiring a more complex API interaction in order for the bot to function, but it should improve the bot's functionality overall.`
- Uses Discord Buttons and Select Menus to replace Reaction Role features

View File

@ -1,7 +1,7 @@
# Geas Server Bot
```
(Currently a work in progress. The bot is still not in a state to run, and none of its features have been programmed..)
(Currently a work in progress. The bot is still not in a state to run, and none of its features have been programmed.)
```
This is a bot I wrote to manage the Discord server for Geas, the Edinburgh University Table-Top Role-Playing Society, during our move to an on-line format.
@ -77,8 +77,8 @@ in order for to authenticate as the correct bot.
|
|-- .env
|-- .gitignore
|-- CHANGELOG.md
|-- COMMANDS.md
|-- CHANGELOG.md
|-- COMMANDS.md
|-- docker-compse.yml
|-- LICENSE
|-- README.md

29
TODO.md
View File

@ -3,39 +3,40 @@
## Bot Architecture
- [x] Simplify directory tree
- [x] Split event listeners into individual cogs.
- [ ] Update with re-organised data and config structure
> - [ ] Correct references to data in existing cogs.
- [x] Update with re-organised data and config structure
> - [x] Correct references to data in existing cogs.
## Bot Functionality
- [ ] 'Delete Commands' Function
- [ ] 'Register Commands' Function
- [ ] Infer Permissions from Config
- [X] Dynamic Command Prefixes
- [x] Dynamic Command Prefixes
- [ ] Infer Games from Server Structure
- [ ] Re-enable logging
- [X] Delete Dev/Test Functions
- [ ] Error handlers
- [x] Delete Dev/Test Functions
- [x] Error handlers
- [ ] Debug Features
- [ ] Help Channel Event Listener
> - [X] Add Config key for Help Channel
- [x] Help Channel Event Listener
> - [x] Add Config key for Help Channel
- [ ] Slash Command Buttons or
- [ ] Reaction listener selectors
- [ ] Member Verification
> - [X] Add Config key membership signup channels
> - [X] Add config keys: Membership Category Roles
> - [x] Add Config key membership signup channels
> - [x] Add config keys: Membership Category Roles
> - [ ] Message Receive listener
> - [ ] Message React listener or buttons
- [ ] Membership Restriction
> - [ ] Message Receive Listener
> - [ ] Membership Validation Listener
- [ ] Re-register commands after any relevant config changes
- [x] Flag for checking completeness of configuration for a guild.
> - [x] Function for checking configs for completeness
## Event Listeners
### Review Configs When
- [X] Guild Changing Ownership
- [X] Roles Modified
- [X] Mod Channel Deleted
## Review Configs When
- [x] Guild Changing Ownership
- [x] Roles Modified
- [x] Mod Channel Deleted
## Commands
- [ ] Configure Bot function and sub commands

View File

@ -7,6 +7,8 @@ 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 discord_components # Additional Discord functions: buttons, menus, etc
from deepdiff import DeepDiff
import logging
## Define YAML functions
@ -22,14 +24,14 @@ def yaml_dump(data:dict, filepath:str):
with open(filepath, 'w') as file:
yaml.dump(data, file)
# Locate or create config file
configFile = os.getenv('CONFIG') if os.getenv('CONFIG').endswith('.yml') else './data/config.yml'
# Locate or create config file. Read from environment variables to locate file, and if missing or not valid then use default location.
configFile = os.getenv('CONFIG') if ((os.getenv('CONFIG').endswith('.yml') or os.getenv('CONFIG').endswith('.yaml')) and not os.getenv('CONFIG').endswith('config_blueprint.yml')) else './data/config.yml'
if not os.path.exists(configFile):
yaml_dump({},configFile)
# Locate or create data file
dataFile = os.getenv('DATA') if os.getenv('DATA').endswith('.yml') else './data/data.yml'
# Locate or create data file. Same as above.
dataFile = os.getenv('DATA') if ((os.getenv('DATA').endswith('.yml') or os.getenv('DATA').endswith('.yaml')) and not os.getenv('DATA').endswith('data_blueprint.yml')) else './data/data.yml'
if not os.path.exists(dataFile):
yaml_dump({},dataFile)
@ -73,31 +75,40 @@ def setConfig(guild:discord.Guild):
guildStr = str(guild.id)
if guildStr not in conf:
conf[guildStr] = {}
if 'name' not in conf[guildStr] or conf[guildStr]['name'] != guild.name:
conf[guildStr]['name'] = guild.name
if 'configured' not in conf[guildStr] or conf[guildStr]['configured'] is not bool:
conf[guildStr]['configured'] = False
if 'owner' not in conf[guildStr] or conf[guildStr]['owner'] != guild.owner_id:
conf[guildStr]['owner'] = guild.owner_id
if 'roles' not in conf[guildStr] or (type(conf[guildStr])['roles'] is not dict or len(conf[guildStr]['roles']) == 0 or None in conf[guildStr]['roles']):
conf[guildStr]['roles'] = {}
if 'admin' not in conf[guildStr]['roles'] or (type(conf[guildStr]['roles']['admin']) is not list or len(conf[guildStr]['roles']['admin']) == 0 or None in conf[guildStr]['roles']['admin']):
conf[guildStr]['roles']['admin'] = []
for role in guild.roles:
if not (role.is_bot_managed() or role.is_integration()) and role.permissions.administrator:
conf[guildStr]['roles']['admin'].append(role.id)
if 'prefix' not in conf[guildStr]:
conf[guildStr]['prefix'] = '-'
if 'modchannel' not in conf[guildStr]:
gDict = conf[guildStr]
if 'channels' not in gDict or gDict['channels'] is not dict or None in gDict['channels']:
gDict['channels'] = {}
cDict = gDict['channels']
if 'mod' not in cDict:
if guild.system_channel is None:
p = len(guild.channels)
c = None
for t in guild.text_channels:
if t.position < p:
p = t.position
conf[guildStr]['modchannel'] = t.id
cDict['mod'] = t.id
else:
conf[guildStr]['modchannel'] = guild.system_channel.id
cDict['mod'] = guild.system_channel.id
if 'configured' not in gDict or gDict['configured'] is not bool:
gDict['configured'] = False
if 'membership' not in gDict or 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
if 'owner' not in gDict or gDict['owner'] != guild.owner_id:
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']):
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']):
rDict['admin'] = []
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']:
gDict['timeslots'] = []
yaml_dump(conf, configFile)
def clearConfig(guildKey:str):
@ -107,6 +118,90 @@ def clearConfig(guildKey:str):
del conf[guildKey]
yaml_dump(conf, configFile)
def checkConfig(guild:discord.Guild):
#### Checks the completeness of the configurations of the current guild. returns (bool of config state, [list of missing keys])
#### The bot does this by comparing the keys and structure in the ./data/config_blueprint.yml file. This offers the bot some level of abstraction in doing this.
#### DeepDiff between the blueprint and the configs, and see if any values have been removed (thus missing)
guildStr = str(guild.id)
conf = yaml_load(configFile)
unconfigured = []
blueprint = yaml_load('./data/config_blueprint.yml')
diff = DeepDiff(blueprint['guild_id_string'], conf[guildStr])
if 'dictionary_item_removed' in diff:
for i in diff['dictionary_item_removed']:
s = i.split("'")
for item in list(i.split("'")):
if '[' in item or ']' in item:
s.remove(item)
unconfigured.append('.'.join(s))
if 'type_changes' in diff:
for i in diff['type_changes']:
s = i.split("'")
for item in list(i.split("'")):
if '[' in item or ']' in item:
s.remove(item)
if 'notifications' not in '.'.join(s):
unconfigured.append('.'.join(s))
if 'iterable_item_removed' in diff:
for i in diff['iterable_item_removed']:
s = i.split("'")
for item in list(i.split("'")):
if '[' in item or ']' in item:
s.remove(item)
unconfigured.append('.'.join(s))
for i in blueprint['guild_id_string']:
if i not in blueprint['guild_id_string']['meta']['strict'] and isinstance(blueprint['guild_id_string'][i], dict):
if i in conf[guildStr] and isinstance(conf[guildStr][i], dict) and len(conf[guildStr][i]) < 1:
unconfigured.append(i)
if 'meta' in unconfigured:
unconfigured.remove('meta')
if 'configured' in unconfigured:
unconfigured.remove('configured')
output = list(set(unconfigured))
if len(output) > 0:
conf[guildStr]['configured'] = False
elif len(output) == 0:
conf[guildStr]['configured'] = True
yaml_dump(conf,configFile)
return conf[guildStr]['configured'], output
def parseConfigCheck(missingKeys: list):
output = 'Configuration values for the following have not been defined:\n\n'
for entry in missingKeys:
if '.' in entry:
e1, e2 = entry.split('.')
if e1 == 'channels':
if e2 == 'help':
output = ''.join([output, f"- The `help channel` for the Bot to monitor and notify Committee\n"])
if e2 == 'mod':
output = ''.join([output, f"- The `moderation channel` for the bot's outputs\n"])
if e2 == 'signup':
output = ''.join([output, f"- The `sign-up channel` for the membershp registration\n"])
if e1 == 'roles':
if e2 == 'admin':
output = ''.join([output, f"- The `administrator` role(s) for the guild\n"])
if e2 == 'committee':
output = ''.join([output, f"- The `Committee` role for the guild\n"])
if e2 == 'bots':
output = ''.join([output, f"- The `Bots` role for the guild\n"])
if e2 == 'newcomer':
output = ''.join([output, f"- The `Newcomer` role for the guild\n"])
if e2 == 'returning':
output = ''.join([output, f"- The `Returning Player` role for the guild\n"])
if e2 == 'student':
output = ''.join([output, f"- The `Student` role for the guild\n"])
if entry == 'membership':
output = ''.join([output, f"- `Membership roles`: the Channel needs at least one membership role\n"])
if entry == 'name':
output = ''.join([output, f"- The guild's `name`\n"])
if entry == 'owner':
output = ''.join([output, f"- The guild's `owner`\n"])
if entry == 'prefix':
output = ''.join([output, f"- The guild's `prefix` for native (non-`/`) commands.\n"])
if entry == 'timeslots':
output = ''.join([output, f"- Available `timeslots` for server games.\n"])
return output
def loadCog(filepath:str):
path = os.path.normpath(filepath).split(os.path.sep)
if path[-1].endswith('.py'):

View File

@ -6,7 +6,7 @@ from bot import configFile, yaml_load, yaml_dump
### Cog for handling the non-Slash prefix for native Bot commands.
class Prefix(commands.Cog):
class Prefix(commands.Cog, name='Server Command Prefix'):
def __init__(self, client):
self.client = client

View File

@ -6,11 +6,13 @@ 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 deepdiff import DeepDiff
from pprint import pprint
from bot import loadCog, unloadCog
from bot import loadCog, unloadCog, checkConfig, parseConfigCheck, yaml_load, configFile
##### Dev Cog
class Dev(commands.Cog):
class Dev(commands.Cog, name='Developer Commands'):
def __init__(self, client):
self.client = client
@ -22,10 +24,28 @@ class Dev(commands.Cog):
async def _debug(self, ctx, toggle:str):
if toggle.lower() == 'on':
loadCog(f'./debug/debug.py')
await ctx.reply(f'Debug commands enabled. Use them carefully.')
await ctx.reply(f'```Debug commands enabled. Use them carefully.```')
elif toggle.lower() == 'off':
unloadCog(f'./debug/debug.py')
await ctx.reply(f'Debug commands disabled.')
await ctx.reply(f'``Debug commands disabled.``')
else:
raise commands.CommandError(message='Invalid argument.')
# await ctx.reply(f'```Invalid argument.```')
@commands.command(
name='testconfig',
description='Tests the completeness of the configuration values of the current guild by comparing it to a configuration blueprint.',
brief='Tests config values for current guild.',
aliases=['configtest']
)
async def _testconfig(self, ctx):
checkConfig(ctx.guild)
status, output = checkConfig(ctx.guild)
conf = yaml_load(configFile)
if not status:
await ctx.reply(f"```The Bot's configurations are incomplete for the guild {ctx.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.```")
elif status:
await ctx.reply(f"```The Bot's configurations for the guild {ctx.guild.name} are in order. The Bot is ready to interact with the guild.```")
def setup(client):
client.add_cog(Dev(client))

View File

@ -0,0 +1,27 @@
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
class on_command_error(commands.Cog, name='On Command Error'):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_command_error(self, ctx, error):
if isinstance(error, discord.DiscordException):
if 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_command_error(client))

View File

@ -9,7 +9,7 @@ import logging
from bot import configFile, yaml_load, yaml_dump
#### Actions for the Bot to take on connecting to Discord.
class on_connect(commands.Cog):
class on_connect(commands.Cog, name='On Connect Events'):
def __init__(self, client):
self.client = client

View File

@ -9,7 +9,7 @@ import logging
from bot import configFile, yaml_load, yaml_dump
##### Actions for the bot to take whenever a channel in a guild is deleted
class on_guild_channel_delete(commands.Cog):
class on_guild_channel_delete(commands.Cog, name='On Guild Channel Delete Events'):
def __init__(self, client):
self.client = client
@ -17,16 +17,16 @@ class on_guild_channel_delete(commands.Cog):
@commands.Cog.listener()
async def on_guild_channel_delete(self, channel):
conf = yaml_load(configFile)
if conf[str(channel.guild.id)]['modchannel'] == channel.id:
if conf[str(channel.guild.id)]['channels']['mod'] == channel.id:
if channel.guild.system_channel is None:
p = len(channel.guild.channels)
c = None
for t in channel.guild.text_channels:
if t.position < p:
p = t.position
conf[str(channel.guild.id)]['modchannel'] = t.id
conf[str(channel.guild.id)]['channels']['mod'] = t.id
else:
conf[str(channel.guild.id)]['modchannel'] = channel.guild.system_channel.id
conf[str(channel.guild.id)]['channels']['mod'] = channel.guild.system_channel.id
yaml_dump(conf, configFile)
def setup(client):

View File

@ -6,17 +6,21 @@ 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 configFile, setConfig, yaml_load, yaml_dump
from bot import checkConfig, parseConfigCheck, configFile, setConfig, yaml_load, yaml_dump
#### Actions for the bot to take when the Bot joins a guild.
class on_guild_join(commands.Cog):
class on_guild_join(commands.Cog, name='On Guild Join Events'):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_guild_join(self, guild):
setConfig(guild)
status, output = checkConfig(guild)
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.")
def setup(client):
client.add_cog(on_guild_join(client))

View File

@ -10,7 +10,7 @@ from bot import clearConfig, configFile, yaml_load, yaml_dump
#### Actions for the bot to take when removed from a guild.
class on_guild_remove(commands.Cog):
class on_guild_remove(commands.Cog, name='On Guild Remove Events'):
def __init__(self, client):
self.client = client

View File

@ -10,7 +10,7 @@ from bot import configFile, yaml_load, yaml_dump
##### Actions for the bot to take whenever there is a new role created.
class on_guild_role_create(commands.Cog):
class on_guild_role_create(commands.Cog, name='On Guild Role Create Events'):
def __init__(self, client):
self.client = client
@ -19,7 +19,7 @@ class on_guild_role_create(commands.Cog):
conf = yaml_load(configFile)
#### Bot will only respond if the role is not a bot-managed role, and the role is an admin role
if not (role.is_bot_managed() or role.is_integration()) and role.permissions.administrator:
conf[str(role.guild.id)]['adminroles'].append(role.id)
conf[str(role.guild.id)]['roles']['admin'].append(role.id)
yaml_dump(conf, configFile)
#### If the role is created with admin privileges, the bot adds the role to its configs.

View File

@ -10,7 +10,7 @@ from bot import configFile, yaml_load, yaml_dump
##### Actions for the bot to take whenever there is a new role deleted.
class on_guild_role_delete(commands.Cog):
class on_guild_role_delete(commands.Cog, name='On Guild Role Delete Events'):
def __init__(self, client):
self.client = client
@ -18,8 +18,8 @@ class on_guild_role_delete(commands.Cog):
async def on_guild_role_delete(self, role):
conf = yaml_load(configFile)
#### Bot will only respond if the role is not a bot-managed role, and the role is an admin role
if role.id in conf[str(role.guild.id)]['adminroles']:
conf[str(role.guild.id)]['adminroles'].remove(role.id)
if role.id in conf[str(role.guild.id)]['roles']['admin']:
conf[str(role.guild.id)]['roles']['adminroles'].remove(role.id)
yaml_dump(conf, configFile)
#### If the role is one of the Admin roles and is deleted, updates the bot's config to delete that role, preventing unnecessary roles from accumulating.

View File

@ -10,7 +10,7 @@ from bot import configFile, yaml_load, yaml_dump
##### Actions for the bot to take whenever there is a new role deleted.
class on_guild_role_update(commands.Cog):
class on_guild_role_update(commands.Cog, name='On Guild Role Update Events'):
def __init__(self, client):
self.client = client
@ -18,15 +18,15 @@ class on_guild_role_update(commands.Cog):
async def on_guild_role_update(self, before, after):
conf = yaml_load(configFile)
#### If the original role is in the config as an admin role, and it subsequently is run by a bot or is not an admin, remove it from config
if before.id in conf[str(before.guild.id)]['adminroles']:
if before.id in conf[str(before.guild.id)]['roles']['admin']:
if after.is_bot_managed() or after.is_integration() or not after.permissions.administrator:
conf[str(after.guild.id)]['adminroles'].remove(after.id)
conf[str(after.guild.id)]['roles']['admin'].remove(after.id)
yaml_dump(conf, configFile)
#### If the new role is an admin and is not already in the config, add it.
if not (after.is_bot_managed() or after.is_integration()) and after.permissions.administrator:
if after.id not in conf[str(after.guild.id)]['adminroles']:
conf[str(after.guild.id)]['adminroles'].remove(after.id)
if after.id not in conf[str(after.guild.id)]['roles']['admin']:
conf[str(after.guild.id)]['roles']['admin'].remove(after.id)
yaml_dump(conf, configFile)
#### If the role is one of the Admin roles and is deleted, updates the bot's config to delete that role, preventing unnecessary roles from accumulating.

View File

@ -9,7 +9,7 @@ import logging
from bot import configFile, yaml_load, yaml_dump
##### Actions for the bot to take whenever the guild info or ownership are updated.
class on_guild_update(commands.Cog):
class on_guild_update(commands.Cog, name='On Guild Update Events'):
def __init__(self, client):
self.client = client

View File

@ -0,0 +1,47 @@
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, tasks # 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 datetime import datetime
import logging
# logger and handler
from bot import configFile, yaml_load
#### Actions the bot will take on messages being sent in the channel.
##### Message Listener Cog
class on_message(commands.Cog, name='On Message Events'):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_message(self,message):
if message.author.bot or message.author.id == message.guild.owner_id:
return
for role in message.author.roles:
if role.permissions.administrator:
return
conf = yaml_load(configFile)
guild = message.guild
guildStr = str(guild.id)
if 'notifications' in conf[guildStr]:
if 'help' in conf[guildStr]['notifications']:
if conf[guildStr]['notifications']['help']:
if 'help' in conf[guildStr]['channels'] and 'committee' in conf[guildStr]['roles']:
if message.channel.id == conf[guildStr]['channels']['help'] and isinstance(guild.get_role(conf[guildStr]['roles']['committee']), discord.Role):
modChannel = self.client.get_channel(conf[guildStr]['channels']['mod'])
committeeRole = guild.get_role(conf[guildStr]['roles']['committee'])
embed = discord.Embed(
title = f'[New Query in Help]({message.jump_url})',
description = message.content,
colour = discord.Colour.orange()
)
embed.set_footer(text=datetime.now().strftime('%a %-d %b %y, %-I:%M %p'))
embed.set_author(name=message.author.display_name, icon_url=message.author.avatar_url)
await modChannel.send(f'{committeeRole.mention}\n```There has been a new help query posted.```\n{message.author.mention}` posted in `{message.channel.mention}`.`', embed = embed)
def setup(client):
client.add_cog(on_message(client))

View File

@ -7,10 +7,10 @@ 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 clearConfig, configFile, setConfig, yaml_dump, yaml_load
from bot import checkConfig, clearConfig, configFile, parseConfigCheck, setConfig, yaml_dump, yaml_load
#### Actions for the Bot to take once it is ready to interact with commands.
class on_ready(commands.Cog):
class on_ready(commands.Cog, name='On Ready Events'):
def __init__(self, client):
self.client = client
@ -26,5 +26,12 @@ class on_ready(commands.Cog):
for key in list(conf):
clearConfig(key)
#### Check completeness of configurations
for guild in self.client.guilds:
status, output = checkConfig(guild)
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.```")
def setup(client):
client.add_cog(on_ready(client))

View File

@ -0,0 +1,23 @@
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 # Slash Command features
import logging
# logger and handler
from bot import configFile, yaml_load
##### Membership Restriction Message Listener Cog
class RestrictionMessageListener(commands.Cog, name='Membership Restriction Message Listener'):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_message(self,message):
if message.author.bot:
return
def setup(client):
client.add_cog(RestrictionMessageListener(client))

View File

@ -1,7 +1,15 @@
'864651943820525609':
adminroles:
- 864661232005939280
modchannel: 865348933022515220
channels:
mod: 865348933022515220
configured: false
membership: {}
name: Test
notifications:
help: null
signup: null
owner: 493694762210033664
prefix: '-'
roles:
admin:
- 864661232005939280
timeslots: []

View File

@ -0,0 +1,30 @@
guild_id_string:
channels: # Dictionary
# help is an optional feature so not necessary in blueprint.
mod: 0
signup: 0
configured: false
membership: {} # Dictionary
# For membership, at least one kind needs to be defined. But no key is mandatory.
name: string
owner: 0
prefix: string
roles: # dictionary
admin:
- 0 # List
# For admins, at least one needs to be defined.
# committee notifications is optional so is not necessary in blueprint.
bots: 0
# newcomer role is optional
# returning player role is optional
# student role is optional
timeslots:
- string # List
# At least one needs to be defined.
meta:
strict:
- channels
- roles
notifications:
help: true
signup: true

View File

@ -10,10 +10,14 @@ from discord_slash.utils.manage_commands import create_choice, create_option # S
from bot import clearConfig, configFile, loadCog, loadCogs, setConfig, unloadCog, unloadCogs, yaml_dump, yaml_load
##### Debug Cog
class Debug(commands.Cog):
class Debug(commands.Cog, name='Debug Commands'):
def __init__(self, client):
self.client = client
#### Permission Check: Only available to the bot's maintainer.
async def cog_check(self, ctx):
return ctx.author.id == int(os.getenv('BOT_MAINTAINER_ID'))
@commands.command(
name='reloadcogs',
description='Reloads cogs within the specified category, or provide `all` for all cogs. Default: `all`.',
@ -22,7 +26,7 @@ class Debug(commands.Cog):
async def _reload(self, ctx, cog_category: str='all'):
unloadCogs(cog_category)
loadCogs(cog_category)
await ctx.repky(f'`{cog_category}` cogs have been reloaded.')
await ctx.reply(f'`{cog_category}` cogs have been reloaded.')
@commands.command(
name='unloadcogs',
@ -32,7 +36,7 @@ class Debug(commands.Cog):
async def _unloadcogs(self, ctx, cog_category: str='all'):
unloadCogs(cog_category)
loadCogs(cog_category)
await ctx.repky(f'`{cog_category}` cogs have been unloaded.')
await ctx.reply(f'`{cog_category}` cogs have been unloaded.')
@commands.command(
name='loadcogs',
@ -42,7 +46,7 @@ class Debug(commands.Cog):
async def _loadcogs(self, ctx, cog_category: str='all'):
unloadCogs(cog_category)
loadCogs(cog_category)
await ctx.repky(f'`{cog_category}` cogs have been loaded.')
await ctx.reply(f'`{cog_category}` cogs have been loaded.')
@commands.command(
name='deletecommands',
@ -65,10 +69,12 @@ class Debug(commands.Cog):
@commands.command(
name='retrievecommands',
aliases=['slashcommands','retrieveslashcommands']
aliases=['slashcommands','retrieveslashcommands'],
description='Debugging command that retrieves all slash commands currently registered for this guild and this bot to the Python console.',
brief='Retrieves registered slash commands to console.'
)
async def _retrievecommands(self, ctx:commands.Context):
c = await utils.manage_commands.get_all_commands(client.user.id,os.getenv('TEST_3_TOKEN'),guild_id=ctx.guild.id)
c = await utils.manage_commands.get_all_commands(self.client.user.id,os.getenv('TEST_3_TOKEN'),guild_id=ctx.guild.id)
print(c)
@commands.command(
@ -81,7 +87,7 @@ class Debug(commands.Cog):
conf = yaml_load(configFile)
for key in list(conf):
clearConfig(key)
ctx.reply(f'Config entries have been cleared.')
await ctx.reply(f'Config entries have been cleared.')
@commands.command(
name='setconfig',
@ -91,7 +97,7 @@ class Debug(commands.Cog):
)
async def _setconfig(self, ctx:commands.Context):
setConfig(ctx.guild)
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))