forked from viveksantayana/geas-bot
Added config checking, event listeners, etc.
Can track added features by comparing TODO list.
This commit is contained in:
parent
1d355e3b2d
commit
51a01bab49
1
.gitignore
vendored
1
.gitignore
vendored
@ -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/*
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
29
TODO.md
29
TODO.md
@ -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
|
||||
|
139
app/bot.py
139
app/bot.py
@ -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'):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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))
|
27
app/cogs/events/on_command_error.py
Normal file
27
app/cogs/events/on_command_error.py
Normal 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))
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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))
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
47
app/cogs/events/on_message.py
Normal file
47
app/cogs/events/on_message.py
Normal 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))
|
@ -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))
|
23
app/cogs/membership_restriction/message_listener.py
Normal file
23
app/cogs/membership_restriction/message_listener.py
Normal 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))
|
@ -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: []
|
||||
|
30
app/data/config_blueprint.yml
Normal file
30
app/data/config_blueprint.yml
Normal 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
|
@ -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))
|
Loading…
Reference in New Issue
Block a user