Finished the bot. Requires testing.

Wrote up documentation. Readme needs finishing.
Future development needs to use global listeners for processes.
This commit is contained in:
Vivek Santayana 2021-07-23 15:55:27 +01:00
parent 2f974f9f8f
commit 5bb9af12c9
9 changed files with 334 additions and 81 deletions

View File

@ -1,5 +1,117 @@
# Bot Commands
A full list of bot commands can be retrieved using the `-help` command in the bot.
A full list of native bot commands can be retrieved using the `-help` command in the bot.
The commands have full descriptions of their function as well as syntax that can be accessed via the help command.
This is a reference file of all of the commands available, as well as the various cogs that control them.
This will not apply to `/commands`, and so this reference file will provide a list of all the commands, the cogs that control them, as well as their syntax or usage notes.
`N.B.: Do not delete any roles, channels, or categories that are created by the Bot unless you are certain you need to.
The Bot has no way of synchronising manual actions with its data files at present, and any conflict between the guild setup and the Bot's data will cause errors.
In a future version, I may try and make the bot more flexible to work with both its own commands and user actions.`
The prefix for the Bot's native commands is dynamic.
It can be changed using the relevant command below.
The default prefix for the bot is `-`.
In the syntax description below, mandatory arguments are given in `<angled brackets>`, and optional arguments are given in `[square brackets]`.
For most purposes, the bot will primarily rely on the new `/command` framework, and as such those commadns will be listed first. The native bot commands will be provided below.
## `/commands`
`/commands` operate through a level of base commands, groups, and sub-commands.
Sub-commands inherit their permissions from their base commands.
All permissions are thus set at the level of the base command.
The commands are given in different cogs in order to keep the design modular, and to ensure that different modules can be activated or deactivated depending on what the configuration of the guild is like.
### Configuration Commands
These commands are to set up the bot for the guild.
The base command for the bot is `/config`.
Configuration commands are `admin only`.
| Command | Description |
|---|---|
| `/config roles <key> <role exists> [role]` | Sets up the various key roles referenced by the bot, such as `bot` for dice bots, `committee`, `students`, as well as `newcomer`/`returning-player`. If a role already exists, the bot will assign that role. If it does not, the bot will create one. The bot will not be able to create games until a dice bot role has been defined.
| `/config channels <key> <channel exists> [channel]` | Sets up the various key channels referenced by the bot, such as the `mod` channel for notifications to Committee, as well as the `help` channel for the bot to monitor, and the `signup` channel for membership confirmations. If a channel already exists, the bot will assign it. If it does not, the bot will create one. The bot will not be able to use these features until the channels are defined. |
| `/config membership add <name> <role exists> [role]` | Creates a new membership type for the Bot to administer in the Guild. If a role exists, it will assign that role to the membership type. If it does not, the bot will create it. |
| `/config membership list` | Shows the current types of membership set up. |
| `/config membership remove <membership role>` | Deletes a membership type set up in the Guild. The command is locked if there are no membership types configured. |
| `/config notifications <key> <value>` | Whether or not the bot notifies the Committee on messages being posted here. This includes monitoring for the `help` channel and `membership signups` channel, etc. |
|`/config restrict <value>` | Enables or disables membership restriction in the Guild. |
| `/config timeslots add <key> <name>` | Creates a new timeslot for the Guild to host games in. The bot will not be able to create games until at least one time slot is configured. |
| `/config timeslots list` | Shows the current timeslots with their keys and names that are configured for the Guild. This command will only display if there is at least one timeslot configured. If you delete the last time slot, this command will be locked. |
| `/config timeslots modify <key> <name>` | Edits the name of a configured timeslot. You cannot change the key of a timeslot. This command will only display if there is at least one timeslot configured. If you delete the last time slot, this command will be locked. |
| `/config timeslots remove` | Opens a prompt to delete a configured timeslot. You cannot delete a time slot that has games configured for it. This command will only display if there is at least one timeslot configured. If you delete the last time slot, this command will be locked. |
### Game Commands
These commands are for setting up and managing games configured on the Guild.
The base command for the bot is `/game`.
The commands are `admin only`.
The base command is locked and only become available when at least one timeslot is set up, and a bot role has been assigned.
The sub-commands have additional restrictions.
| Command | Description |
|---|---|
| `/game create ...` | This is the base command to create games. This command takes several parameters that are far too many for me to list here. The Prompt system will make it clear what arguments need to be entered and when. |
| `/game delete <@game role>` | Deletes the game associated with the role that is mentioned. This command is locked until there is at least one game configured. If the last game that is configured is deleted by the bot, the command is locked again. `This command must be issued in a text channel associated with the game being deleted (in addition to mentioning the game role).` |
| `/game modify <@game role> ...` | This command also takes several parameters, just like `/game create`, but all of them are optional as long as at least one is entered. It changes the various parameters of the game. |
| `/game purge` | Opens a prompt to delete all games in a given time slot. It also has the option of deleting all games for all time slots. `Use this command with extreme caution.` |
### Player Commands
These commands are for anaging players and their membership of games.
The base command is `/player`.
Permissions for this command are open to all users (i.e. all users who have the `use slash command` permission enabled on the Guild settings).
These commands are locked until there is at least one game configured.
| Command | Description |
|---|---|
| `/player add <@player> <@game>` | Adds a player to a game. It can only be used by the GM of the game being mentioned, or by an admin. |
| `/player leave` | Removes the user issuing the command from the game they are in. This command `must be issued in a text channel associated with the game you are trying to leave`. |
| `/player remove <@player>` | Removes the user mentioned from the game. This command `must be issued in a text channel associated with the game you are trying to leave`. |
### Pitch Command
The `/pitch` command is used to run pitches for games on the server.
It is locked until at least one game is set up.
The command is `admin only`.
It turns the current text channel where it is issued into a pitch listing channel, setting its permissions appropriately.
It opens a prompt to select which timeslot to pitch for.
The command further generates a menu of the games running, and also gives the admins a control panel with which to control access to the game staggered by the various roles (newcomers, returning players) if they are defined.
The pitch menu runs until it is closed using the relevant button.
## Native Bot `-commands`
These commands are issued using the bot's text prefix.
The prefix may vary depending on the bot's configuration.
### Default Commands
These commands are default to the `Discord.py` library and are automatically enabled in the bot
| Command | Permissions | Syntax and Aliases | Description |
|---|---|---| --- |
| help | All Users | `-help [command]`| Default Discord helper command. Gives a list of all of the commands enabled in the Guild. And when used with an optional argument that gives a command name, it provides the help text, description, and syntax of the command.|
### Control Commands
These commands are found in the file `./cogs/controlcommands/control.py` and provide meta-controls for how the bot and its functionality work.
The permissions for these commands has been set up at the Cog level.
| Command | Permissions | Syntax and Aliases | Description |
|---|---|---| --- |
| debug | Admin Only | `-debug on/off`| Enables debug features for the bot. Debug features can be used by the Bot's maintainer, as set up during configuration. Activating debug features will need to be authorised by an Admin.|
| testconfig | Admin Only | `-testconfig` or `-configtest` | This command checks the completeness of the configurations of the Guild against the blueprint. |
### Prefix Command
This command is in the file `./cogs/botcommands/prefix.py`.
It allows for the dynamic control of the bot's command prefix for native commands.
The permission has been set up at the Cog level.
| Command | Permissions | Syntax and Aliases | Description |
|---|---|---| --- |
| changeprefix | Admin Only | `-changeprefix [str]` or `-prefix [str]` | Sets the given string as a prefix. The argument is option, and if not provided an argument it sets the prefix to `-` as the default. |

View File

@ -31,6 +31,9 @@ BOT_TOKEN=(API token for the production version of the bot.)
TEST_TOKEN=(API token for any test instance.)
CONFIG=(Path to config file. The bot defaults to './data/config.yml' if not provided.)
DATA=(Path to data file. The bot defaults to './data/data.yml' if not provided.)
LOOKUP=(Path to the game role lookup file. The bot defaults to './data/lookup.yml' if not provided.)
GM=(Path to the GM lookup file. The bot defaults to './data/gm.yml' if not provided.)
CATEGORIES=(Path to the channel category lookup file. The bot defaults to './data/categories.yml' if not provided.)
BOT_VERSION=(verson string)
BOT_MAINTAINER_ID=(Discord user ID of the person maintaining the bot to enable debug features.)
```

37
TODO.md
View File

@ -33,31 +33,36 @@
- [ ] ~~Slash Command Buttons or~~ `This kind of got subsumed into other features.`
- [ ] ~~Reaction listener selectors~~ `So did this.`
- [ ] Member Verification
- [x] Member Verification
> - [x] Add Config key membership signup channels
> - [x] Add config keys: Membership Category Roles
> - [ ] Message Receive listener
> - [ ] Message React listener or buttons
> - [x] Message Receive listener
> - [x] ~~Message React listener~~ or buttons `Used buttons waiting within the same command thread. Possibly update to global listener if performance is affected.`
- [ ] Membership Restriction
- [x] Membership Restriction
> - [ ] Message Receive Listener
> - [ ] Membership Validation Listener
> - [x] Message Receive Listener
> - [x] Membership Validation Listener
- [ ] Re-synchronise commands after any relevant config changes `(See from above)`
- [x] Re-synchronise commands after any relevant config changes `(See from above)`
> - [ ] Role Delete (member, admin, game)
> - [ ] Channel delete (notifications, logs, game text channel)
> - [ ] Category delete (games)
> - [ ] Role Delete (~~member~~, ~~admin~~, ~~game~~) `Admin role missing won't cause any issues as server role will still remain in control`
`Deleting membership roles will also trigger a mess. Event listener will trigger and attempt to execute simultaneously with the command to delete the role, which will cascade into several errors. In order to make this work, it is best to have the commands interact solely with the roles, and then subsequently the listeners to sync with the data.`
> - [ ] ~~Channel delete (notifications, logs, game text channel)~~ `There aren't any critical settings that depend on this just yet, and it is hard to set this up without circularity`
> - [ ] ~~Category delete (games)~~ `Circularity problem: if the category delete event listener is set up, it will react to the bot deleting categories when managing games.`
- [x] Flag for checking completeness of configuration for a guild.
> - [x] Function for checking configs for completeness
- [ ] Synchronise game channel on role updates
- [ ] ~~Synchronise game channel on role updates~~
> - [ ] Exception to event listener to prevent circularity `unsure what this means`
> - [ ] ~~Exception to event listener to prevent circularity~~ ~~`unsure what this means`~~
`I remember what this is now: if you have a listener for when roles get deleted, and if the bot deletes a role, then it triggers the listener. It might be worth restructuring this such that the Bot command only deletes the role, which then triggers the listener that deletes the categories and synchronises data. That way, the roles can be deleted manually or via the command. Otherwise, it is not possible in the Discord API to have this exception, as the Bot only responds to the 'Guild' deleting the role, not the user or bot or admin that issued the command.`
## Event Listeners
@ -119,8 +124,8 @@ Do the opposite: block deleting timeslots with existing games.
- [ ] Review documentation
> - [ ] Finalise README.md
> - [ ] CHANGELOG.md
> - [ ] COMMANDS.md
> - [ ] resources.md
> - [x] CHANGELOG.md
> - [x] COMMANDS.md
> - [x] resources.md
- [ ] Make sure to document `not using discord_components and staying with discord-py-slash-commands library alone`.
- [x] Make sure to document `not using discord_components and staying with discord-py-slash-commands library alone`.

View File

@ -28,20 +28,19 @@ class on_message(commands.Cog, name='On Message Events'):
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)
if conf[guildStr]['notifications'].get('help', False):
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

@ -0,0 +1,109 @@
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_components import create_select, create_select_option, create_actionrow, wait_for_component, create_button, create_actionrow, create_choice, create_option
from discord_slash.model import ButtonStyle
import logging
# logger and handler
from bot import configFile, yaml_load, categoriesFile, configFile, lookupFile
##### Membership Verification Cog
class MemberVerification(commands.Cog, name='Member Verification Cog'):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_message(self, message):
conf = yaml_load(configFile)
categories = yaml_load(categoriesFile)
guildStr = str(message.guild.id)
lookup = yaml_load(lookupFile)
if conf[guildStr]['channels'].get('signup', None) is not None: return
if message.channel.id != conf[guildStr]['channels']['signup']: return
if not message.attachments:
await message.author.send(f'```Error: The message you posted in the `{message.channel.name}` channel of the guild `{message.guild.name}` was invalid. Your post must contain a screensot of your proof of purchase for membership.```')
await message.delete()
return
membership = [discord.utils.get(message.guild.roles, id=x) for x in conf[guildStr]['membership']]
membership_options = [create_select_option(label=x.name, value=str(x.id), description='Membership type.') for x in membership]
admin_buttons = []
if conf[guildStr]['roles'].get('student', None) is not None: admin_buttons.append(create_button(style=ButtonStyle.blurple, label='Student', emoji='📚', custom_id=f'student_{message.id}'))
admin_buttons.append(create_button(style=ButtonStyle.grey, label='Alert', emoji='⚠️', custom_id=f'alert_{message.id}'))
admin_buttons.append(create_button(style=ButtonStyle.red, label='Deny', emoji='✖️', custom_id=f'deny_{message.id}'))
admin_buttons.append(create_button(style=ButtonStyle.green, label='Done', emoji='▶️', custom_id=f'done_{message.id}'))
o = f'```For Administrators: Please verify the membership request submitted.```\n'
admins = '|'.join([discord.utils.get(message.guild.roles, id=x).mention for x in conf[guildStr]['roles']['admin']])
o = '\n'.join((o,admins))
m = await message.reply(
content= o,
components=[
create_actionrow(
create_select(
options=membership_options,
custom_id=f'membership_{message.id}',
placeholder=f'Please select a membership to assign to {message.author.display_name}',
min_values=1,
max_values=1
)
),
create_actionrow(
*admin_buttons
)
]
)
while True:
interaction_ctx = await wait_for_component(self.client, messages=m)
if not (set(interaction_ctx.author.roles) & set([interaction_ctx.guild.get_role(x) for x in conf[str(interaction_ctx.guild.id)]['roles']['admin']]) or interaction_ctx.author == interaction_ctx.guild.owner):
await interaction_ctx.send(f'```Error: You are not authorised to assign memberships for guild `{interaction_ctx.guild.name}`. Only administrators may assign memberships using this interface.```', hidden=True)
else:
submission = await interaction_ctx.channel.fetch_message(int(interaction_ctx.custom_id.split('_',1)[1]))
if interaction_ctx.custom_id.startswith('done_'):
await interaction_ctx.send(f'```Membership verification complete.```', hidden=True)
break
elif interaction_ctx.custom_id.startswith('deny_'):
await interaction_ctx.send(f'```Membership verification denied.```', hidden=True)
embed = discord.Embed(
title = submission.author.name,
description = f'[Jup to Message]({submission.jump_url})',
colour = discord.Colour.red(),
)
await submission.author.send(f'```Your membership for guild `{submission.guild.name}` could not be verified. Please make sure your name and the kind of membership that you have bought are visible in the screenshot you upload. Please contact a Committee member if you have any difficulties.```')
if conf[guildStr]['channels'].get('mod', None) is not None:
await submission.guild.get_channel(conf[guildStr]['channels']['mod']).send(f'```Verifying the membership of {submission.author.display_name} failed.```\n{admins}', embed=embed)
break
elif interaction_ctx.custom_id.startswith('alert_'):
await interaction_ctx.send(f'```Membership verification alert raised.```', hidden=True)
embed = discord.Embed(
title = submission.author.name,
description = f'[Jup to Message]({submission.jump_url})',
colour = discord.Colour.orange()
)
await submission.author.send(f'```Your membership for guild `{submission.guild.name}` needs to be reviewed by a Committee member.```')
if conf[guildStr]['channels'].get('mod', None) is not None:
await submission.guild.get_channel(conf[guildStr]['channels']['mod']).send(f'```There is a problem verifying the membership of {submission.author.display_name}.\nCould someone verify this person\'s membership manually via the EUSA portal?.```\n{admins}', embed=embed)
elif interaction_ctx.custom_id.startswith('student_'):
await interaction_ctx.send(f'````Student` role granted.```', hidden=True)
student_role = submission.guild.get_role(conf[guildStr]['roles']['student'])
await submission.author.add_roles(student_role, reason=f'Membership Verification: Student role assigned by `{interaction_ctx.author.display_name}`.')
await submission.author.send(f'```You have additionally been assigned the role `Student` in the guild `{submission.guild.name}`.```')
elif interaction_ctx.custom_id.startswith('membership_'):
[selected_membership] = interaction_ctx.selected_options
selected_role = interaction_ctx.guild.get_role(int(selected_membership))
if selected_role not in submission.author.roles:
await interaction_ctx.send(f'```Membership `{selected_role.name}` added to member `{submission.author.display_name}`.```', hidden=True)
await submission.author.add_roles(selected_role, reason=f'Membership Verification: Membership verified by `{interaction_ctx.author.display_name}`.')
await submission.author.send(f'```Your membership for guild `{submission.guild.name}` has been verified and you have been assigned the role `{selected_role.name}`.```')
else:
await interaction_ctx.send(f'```Membership `{selected_role.name}` removed from member `{submission.author.display_name}`.```', hidden=True)
await submission.author.remove_roles(selected_role, reason=f'Membership Verification: Membership removed by `{interaction_ctx.author.display_name}`.')
await submission.author.send(f'```Your role `{selected_role.name}` has been removed in the guild `{submission.guild.name}`.```')
else:
pass
await m.delete()
def setup(client):
client.add_cog(MemberVerification(client))

View File

@ -0,0 +1,50 @@
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, categoriesFile, configFile, lookupFile
##### Membership Restriction Message Listener Cog
class RestrictionListener(commands.Cog, name='Membership Restriction Listener'):
def __init__(self, client):
self.client = client
# Block non-verified user from posting messages.
@commands.Cog.listener()
async def on_message(self,message):
conf = yaml_load(configFile)
categories = yaml_load(categoriesFile)
guildStr = str(message.guild.id)
lookup = yaml_load(lookupFile)
if conf[guildStr].get('restrict',False): return
if message.author.bot: return
if str(message.channel.category) not in categories[guildStr]: return
if (set(message.author.roles) & set([message.guild.get_role(x) for x in conf[guildStr]['roles']['admin']]) or message.author == message.guild.owner): return
if set(message.author.roles) & set([message.guild.get_role(x) for x in conf[guildStr]['membership']]): return
if message.channel.overwrites_for(message.author).manage_channels: return
signupChannel = discord.utils.find(lambda x: x.id == conf[guildStr]['channels']['signup'], message.guild.channels)
await message.channel.send(f'```{message.author.display_name} does not have a verified membership of `{message.guild.name}`. Please submit your membership confirmation for verification in the {signupChannel.name} to ensure you have access to your game.```\n{message.author.mention} | {signupChannel.mention}')
await message.channel.category.set_permissions(message.author, send_messages = False, connect = False, reason=f'Membership Restriction: {message.author.display_name} is not a verified member.')
await message.delete()
# Reinstate on verification
@commands.Cog.listener()
async def on_member_update(self, before, after):
if before.roles == after.roles: return
conf = yaml_load(configFile)
categories = yaml_load(categoriesFile)
guildStr = str(after.guild.id)
lookup = yaml_load(lookupFile)
if not set(after.author.roles) & set([after.guild.get_role(x) for x in conf[guildStr]['membership']]): return
for game in list(set(after.author.roles) & set([after.guild.get_role(int(x)) for x in lookup[guildStr]])):
c = discord.utils.get(lambda x: x.id == lookup[guildStr][str(game.id)]['category'])
if c is not None:
if c.overwrites_for(after).send_messages is False: await c.set_permissions(after, overwrite = False, reason= f'Membership Restriction: {after.display_name} has been verified and reinstated.')
def setup(client):
client.add_cog(RestrictionListener(client))

View File

@ -1,23 +0,0 @@
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

@ -373,39 +373,34 @@ class Configuration(commands.Cog, name='Configuration Commands'):
loadCog(f'./{cogsDir}/slashcommands/secondary/edit_membership.py')
await self.client.slash.sync_all_commands()
@cog_ext.cog_slash(
name='test',
description='testing options',
@cog_ext.cog_subcommand(
base='config',
# subcommand_group='notifications',
name='restrict',
description='Toggle membership estriction for the guild.',
# base_description='Commands for configuring the various parameters of the Guild',
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Configures whether the bot monitors and responds to posts in key channels.',
guild_ids=guild_ids,
options=[
create_option(
name='required',
description='This option is mandatory',
option_type=3,
name='value',
description='Enable membership restrictions for the guild?',
option_type=5,
required=True
),
create_option(
name='not_required',
description='This option is not mandatory',
option_type=3,
required=False
),
create_option(
name='optional',
description='This option is optional',
option_type=3,
required=False
)
]
)
async def _test(
async def _restrict(
self,
ctx:SlashContext,
required:str,
not_required:str=None,
optional:str=None
value:bool
):
await ctx.send(f'```You entered: name = {required}, not_required = {not_required}, and optional = {optional}```',hidden=True)
conf = yaml_load(configFile)
conf[str(ctx.guild.id)]['restrict'] = value
yaml_dump(conf, configFile)
await ctx.send(f'```Membership restrictions for the guild `{ctx.guild.name}` have been set to `{value}`.```',hidden=True)
def setup(client):
client.add_cog(Configuration(client))

View File

@ -17,8 +17,13 @@
`N.B.: this is an important security feature in Discord's API, and commands will not be configured unless the applications.commands scope is configured correctly.`
> 2. [How to add Slash Commands, including sub-commands](https://discord-py-slash-command.readthedocs.io/en/latest/faq.html#:~:text=If%20your%20slash%20commands%20don,commands%20scope%20in%20that%20guild.)
> 3. [Slash Command Cogs Module](https://discord-py-slash-command.readthedocs.io/en/latest/discord_slash.cog_ext.html?highlight=cog#discord_slash.cog_ext.cog_subcommand)
> 4. [Discord Py Slach Commands library Components Documentation](https://discord-py-slash-command.readthedocs.io/en/latest/components.html)
`N.B.: Components are what Discord calls buttons and drop down menus.
There are multiple libraries that offer Components integration, but the Discord Py Slash Command is adequate in doing so, and has the clearest documentation so far.
It's best to stick to one library to keep it consistent.
Avoid the Discord_components library as it causes conflicts with Discord Py Slash Commands.`
3. [Discord Components Documentation](https://discord-components.readthedocs.io/en/0.5.2.4/)
## YouTube Tutorials
@ -26,8 +31,6 @@
2. [Introduction to Cogs](https://www.youtube.com/watch?v=vQw8cFfZPx0)
3. [Dynamic prefixes for different servers](https://www.youtube.com/watch?v=yrHbGhem6I4)
4. [Using the new Slash Command API](https://www.youtube.com/watch?v=CLQ8gfb2jh4)
5. [Using embeds with Discord buttons](https://www.youtube.com/watch?v=lDZE_Muwgio)
6. [Discord Buttons](https://www.youtube.com/watch?v=oAlTicmBwOk)
## Communities