Compare commits

..

1 Commits

Author SHA1 Message Date
0f447264ec This branch will not work.
Purging not possible because data files keep getting overwritten
by parallel processes.
2021-07-24 11:31:21 +01:00
34 changed files with 666 additions and 670 deletions

3
.gitignore vendored
View File

@ -1,8 +1,5 @@
db/*
**/data/*.yml
!**/data/config_blueprint.yml
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@ -1,9 +1,5 @@
# Changelog
## Minor Updates in Version 3.0.1
- Re-builds the Docker image to use a `python:slim` base instead of `python:buster`, reducing the footprint of the image to a quarter of its previous size.
## Major Changes in Version 3
- Discards the database engine in favour of data storage in `.yml` files

View File

@ -14,7 +14,7 @@ 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 commands will be listed first. The native bot commands will be provided below.
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`
@ -60,7 +60,7 @@ The sub-commands have additional restrictions.
### Player Commands
These commands are for managing players and their membership of games.
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.
@ -71,12 +71,6 @@ These commands are locked until there is at least one game configured.
| `/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`. |
While this is not strictly a `/player` command, and it is housed in a separate cog, it has the same level of permissions and prerequisites as all the `/player` commands.
| Command | Description |
|---|---|
|`/tcard`| Invokes a T-Card in the game. This command also posts a graphic of the T-Card, tags the game's role, and pings a message in the appropriate voice channel.|
### Pitch Command
The `/pitch` command is used to run pitches for games on the server.
@ -102,12 +96,16 @@ These commands are default to the `Discord.py` library and are automatically ena
|---|---|---| --- |
| 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.|
### Debug Commands
### Control Commands
These commands are found in the file `./cogs/controlcommands/debug.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 for those with the Bot Maintainer role only or for the owner of the Guild. If no bot maintainer role is defined, the command will fail.
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.
Information about debug commands will be displayed to users authorised to use them via the `-help` command.
| 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.|
| migrate | Bot Maintainer Only | `-migrate`| Migrates the guild data from the old bot to the new bot by inferring information from the roles and categories on the server. `After data migration has been completed, please uninstall the migrate.py cog to prevent future data clashes`.|
| testconfig | Admin Only | `-testconfig` or `-configtest` | This command checks the completeness of the configurations of the Guild against the blueprint. |
### Prefix Command

View File

@ -17,26 +17,10 @@ It no longer uses a database engine because it never really benefitted from the
The bot authenticates using an API key, which I have kept private in a `.env` file that I have not uploaded to the repository.
In order to set up your own instance of the bot, you will need to provide the following values in a `.env` file in the root directory.
The following is a step-by-step guide on installation:
### Prerequisites
1. **Prerequisites**:
1. Command line access to your computer.
2. Make sure that the Bot has the correct authorisation scopes in your Discord Developer section. The bot needs to have `application.commands` and `bot` enabled.
3. Invite the Bot to join your guild.
4. Install and run [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git), [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
2. Make a folder to house the project. `mkdir geas-bot && cd geas-bot`
3. Clone the repository. Type `git clone https://git.vsnt.uk/viveksantayana/geas-bot.git .`
4. Navigate to the `app` folder and use the `.env.example` file to make the environment variables file: `sudo mv .env.example .env`
5. Edit the `.env` file and add the relevant API keys (see below). You can do this using any text editor, like `sudo nano .env`.
6. Place any data files from a previous install in the relevant `/app/data` folder. They will be copied in to the Docker container in the next step. Make sure the `.env` files refer to them correctly.
7. Navigate back to the root folder `geas-bot` and execute docker-compose to spin up the Bot. Do this in detached mode so you do not get locked into the terminal (unless you want to debug and read the console outputs.) `sudo docker-compose up -d`
8. Sign in to Discord and finish configuring the bot using the various `/config` commands.
The following is the template for the `.env` file, with the variable names as are referenced in the bot's code.
You can find an example template of this in the `.env.example` file.
You will also need this database to set up a username and password for the MongoDB database.
The specific username and password don't matter as the bot refers back to the environment variable when authenticating.
The following is the template for the `.env` file, with the variable names as are referenced in the bot's code:
`.env` file:
```DotENV
@ -47,13 +31,14 @@ 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.)
PITCHES=(Path to the pitches data file. The bot defaults to './data/pitches.yml' if not provided.)
BOT_VERSION=(verson string)
BOT_MAINTAINER_ID=(Discord user ID of the person maintaining the bot to enable debug features.)
```
The correct API keys need to be entered in the environment variables in the `.env` file, and for a copy of this file to be placed in the root and the `app` directories.
**N.B.**: You can switch between which Both the code executes, the main bot or the test bot, by changing the variable referenced in the `client.run()` command in the `bot.py` file:
**N.B.**: When the bot is first run, it is configured to log in as the Test Bot, and not the main Geas Server Bot, as a safety measure.
To change this, navigate to the last line of the file `bot.py` and change the line:
```py
client.run(os.getenv('TEST_TOKEN'))
@ -71,15 +56,13 @@ in order for to authenticate as the correct bot.
```
|-- app
| |-- .env.example
| |-- assets
| | `-- tcard.wav
| |-- .env
| |-- bot.py
| |-- cogs
| | |-- botcommands
| | | `-- prefix.py
| | |-- controlcommands
| | | `-- debug.py
| | | `-- control.py
| | |-- events
| | | |-- on_command_error.py
| | | |-- on_connect.py
@ -91,9 +74,7 @@ in order for to authenticate as the correct bot.
| | | |-- on_guild_role_update.py
| | | |-- on_guild_update.py
| | | |-- on_message.py
| | | |-- on_ready.py
| | | `-- secondary
| | | `-- pitch_listener.py
| | | `-- on_ready.py
| | |-- membership
| | | |-- membership_verification.py
| | | `-- restriction_listener.py
@ -105,8 +86,7 @@ in order for to authenticate as the correct bot.
| | |-- game_management.py
| | |-- manipulate_timeslots.py
| | |-- pitch.py
| | |-- player_commands.py
| | `-- tcard.py
| | `-- player_commands.py
| |-- data
| | |-- .gitkeep
| | |-- categories.yml
@ -115,6 +95,8 @@ in order for to authenticate as the correct bot.
| | |-- data.yml
| | |-- gm.yml
| | `-- lookup.yml
| |-- debug
| | `-- debug.py
| |-- Dockerfile
| `-- requirements.txt
|-- CHANGELOG.md
@ -132,7 +114,7 @@ The `COMMANDS.md` file gives a list of all the commands the Bot uses, as well as
The bot holds data in two `.yml` files, `config.yml` for client configurations for each guild it is in and `data.yml` to hold the actual data regarding game and channel set-up.
I was considering merging them into one file, but given how different the two concerns were I ended up splitting the files.
I had initially considered a `.ini` file for the configuration settings and `.json` for the data, but I decided to use `.yml` for both just to avoid unnecessary complexity.
I had initially condsiders a `.ini` file for the configuration settings and `.json` for the data, but I decided to use `.yml` for both just to avoid unnecessary complexity.
### `config.yml` Structure
@ -212,13 +194,8 @@ There is currently no way of having an exception for the Bot's edits.
To reconcile this, the bot would need to work such that the command process that modified games only acted upon the roles, which would then trigger the event listeners to synchronise these changes with the categories, and subsequently the data.
Having the bot edit the data in the main command process would mean that there would be conflicts with the simuntaneous execution of parallel threads.
This works for individual commands, but it breaks down when trying to use the `purge` command because of conflicts causedb by simultaneous changes to the data files.
Programming around this will need a further layer of complexity, involving flags checking for R/W operations and a time-out.
### Membership sign up performance issues
I have set the member verification prompt to use a global listener to avoid a situation where it creates several backlogged processes when multiple people post sign-ups at the same time.
This should also mean that the sign-up prompts should persist over reboots.
Other developers for Discord Components recommended the use of dynamic call-backs, however these do not persist after reboots so are not suitable for this purpose.
Discord will need to make substantial API updates in the future for rolling out the Threads feature.
The bot will need updating again then.
The way the membership signup prompt works is that it creates a new instance of the process executing for each member who submits a verification request, and the command runs until the verification is complete (either by verifying it or rejecting it).
This means that there is a risk that several active instances of the command will run simultaneously if a lot of members submit membership confirmation at once.
This should probably also be changed to being a global event listener, with the requisite inforation being passed to the function in the event listener via the custom values of the buttons and drop-down menu options.

137
TODO.md
View File

@ -1,11 +1,136 @@
# To Do for Version 3.1
# To Do
## Docker
- [ ] Testing
- [ ] Dockerise
- [ ] Infer/Transfer data from old bot
- [ ] Deploy
- [x] Re-build using `python:slim` as base image
## Bot Architecture
- [x] Simplify directory tree
- [x] Split event listeners into individual cogs.
- [x] Update with re-organised data and config structure
> - [x] Correct references to data in existing cogs.
- [x] Setup minimally functioning configs of guild on startup
- [x] Synchronise core configuration `/commands` on startup
- [ ] ~~Synchronise secondary `/commands` on complete configuration~~ ``(see below)``
## Bot Functionality and Processes
- [ ] Add support for Discord Threads following Discord Py library update
- [ ] Update permission settings for GMs on game channels to support threads
- [ ] ~~Change component response architecture to use dynamic callback functions instead of global listeners.~~ Not suitable because it does not provide persistence across reboots.
- [ ] ~~'Delete Commands' Function~~
- [ ] ~~'Register Commands' Function~~
- [x] Infer Permissions from Config
- [x] Dynamic Command Prefixes
- [ ] Infer current games from Server Structure
`Create a separate cog to do this instead of having a migrate command, install the cog temporarily and remove the cog once migration is done.`
- [ ] Re-enable logging
- [x] Delete Dev/Test Functions
- [x] Error handlers
- [x] Debug Features
> - [ ] ~~Command Installer/Uninstaller~~
- [x] Help Channel Event Listener
> - [x] Add Config key for Help Channel
- [ ] ~~Slash Command Buttons or~~ `This kind of got subsumed into other features.`
- [ ] ~~Reaction listener selectors~~ `So did this.`
- [x] Member Verification
> - [x] Add Config key membership signup channels
> - [x] Add config keys: Membership Category Roles
> - [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.`
- [x] Membership Restriction
> - [x] Message Receive Listener
> - [x] Membership Validation Listener
- [x] Re-synchronise commands after any relevant config changes `(See from above)`
> - [ ] 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~~
> - [ ] ~~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
## Review Configs When
- [x] Guild Changing Ownership
- [x] Roles Modified
- [x] Mod Channel Deleted
## Commands
- [x] Configure Bot function and sub commands
> - [x] botrole (role group)
> - [x] committeerole (role group)
> - [x] modchannel (channel group)
> - [x] help channel (channel group)
> - [x] signup channel (channel group)
> - [x] newcomer role (role group)
> - [x] returning player role (role group)
> - [x] student role (role group)
> - [x] help notifications (notification group)
> - [x] signup notifications (notification group)
- [x] Set up timeslots
- [x] Delete timeslots
> - [x] Base command
> - [x] ~~Delete all games with the timeslot~~
Do the opposite: block deleting timeslots with existing games.
- [x] List timeslots
- [x] Set up command permissions
> - [x] Slash Commands
>> - [x] Admin Commands
>> - [x] Game Management Commands
> - [x] Native Bot Commands
- [x] Migrate existing bot commands
> - [x] setupgame
> - [x] ~~definebotrole~~ config
> - [x] deletegame
> - [x] ~~reset~~ purge
> - [ ] ~~migrate~~ `See above`
> - [x] ~~kickplayer~~ `/player remove`
> - [x] ~~addplayer~~ `/player add`
> - [x] ~~leavegame~~ `/player leave`
> - [x] Pitch command and sub-commands
> > - [ ] ~~run~~ `Combined both sub-commands into single command and prompt response.`
> > - [ ] ~~clear~~
## Misc
- [x] Review documentation
> - [x] Finalise README.md
> - [x] CHANGELOG.md
> - [x] COMMANDS.md
> - [x] resources.md
- [x] Make sure to document `not using discord_components and staying with discord-py-slash-commands library alone`.

View File

@ -1,9 +0,0 @@
BOT_TOKEN=
TEST_TOKEN=
CONFIG=./data/config.yml
DATA=./data/data.yml
LOOKUP=./data/lookup.yml
GM=./data/gm.yml
CATEGORIES=./data/categories.yml
PITCHES=./data/pitches.yml
BOT_VERSION=3.0.0

View File

@ -1,7 +1,6 @@
FROM python:3.9.6-slim
FROM python:3.8.6-buster
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update -y && apt-get upgrade -y && apt-get install libopus0 -y && \
pip install --upgrade pip setuptools wheel && pip install -r requirements.txt && \
apt-get autoremove -y
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
CMD python3 -u ./bot.py

Binary file not shown.

View File

@ -27,45 +27,46 @@ def yaml_dump(data:dict, filepath:str):
# 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)
if not os.path.exists(configFile):
yaml_dump({},configFile)
# 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)
if not os.path.exists(dataFile):
yaml_dump({},dataFile)
# Locate or create lookup file. Same as above.
lookupFile = os.getenv('LOOKUP') if os.getenv('LOOKUP').endswith('.yml') or os.getenv('LOOKUP').endswith('.yaml') else './data/lookup.yml'
if not os.path.exists(lookupFile): yaml_dump({},lookupFile)
if not os.path.exists(lookupFile):
yaml_dump({},lookupFile)
# Locate or create GM lookup file. Same as above.
gmFile = os.getenv('GM') if os.getenv('GM').endswith('.yml') or os.getenv('GM').endswith('.yaml') else './data/gm.yml'
if not os.path.exists(gmFile): yaml_dump({},gmFile)
if not os.path.exists(gmFile):
yaml_dump({},gmFile)
# Locate or create Categories lookup file. Same as above.
categoriesFile = os.getenv('CATEGORIES') if os.getenv('CATEGORIES').endswith('.yml') or os.getenv('CATEGORIES').endswith('.yaml') else './data/categories.yml'
if not os.path.exists(categoriesFile): yaml_dump({},categoriesFile)
if not os.path.exists(categoriesFile):
yaml_dump({},categoriesFile)
# Locate or create Pitches data file. Same as above.
pitchesFile = os.getenv('PITCHES') if os.getenv('PITCHES').endswith('.yml') or os.getenv('PITCHES').endswith('.yaml') else './data/pitches.yml'
if not os.path.exists(pitchesFile): yaml_dump({},pitchesFile)
l = [dataFile, lookupFile, gmFile, categoriesFile, configFile, pitchesFile]
l = [dataFile, lookupFile, gmFile, categoriesFile]
if len(set(l)) != len(l): raise Exception('Config Error: there is a clash between two file names.')
# Locate Cogs Directory
cogsDir = 'cogs'
## Logging configuration imported boilerplate from Discord Py Docs
logger = logging.getLogger('discord')
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='discord.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
logger.addHandler(handler)
# --> Temporary disable logging because of verboseness.
# ## Logging configuration imported boilerplate from Discord Py Docs
# logger = logging.getLogger('discord')
# logger.setLevel(logging.DEBUG)
# handler = logging.FileHandler(filename='discord.log', encoding='utf-8', mode='w')
# handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
# logger.addHandler(handler)
#### Dynamic Prefixes
def getPrefix(client, message):
@ -75,8 +76,7 @@ def getPrefix(client, message):
# Define Clients
client = commands.Bot(
intents=discord.Intents.all(),
command_prefix=getPrefix,
description=f'Geas Server Bot v.{os.getenv("BOT_VERSION")}.\n\nThis bot designed to automate the management of key features of the Geas Discord Server. It is written by Vivek Santayana. You can find the source code at https://git.vsnt.uk/viveksantayana/geas-bot.'
command_prefix=getPrefix
)
slash = SlashCommand(
client,
@ -267,7 +267,6 @@ def reloadCogs(cogClass:str = '--all'):
loadCogs('controlcommands')
loadCogs('events')
loadCogs('membership')
loadCogs('botcommands')
loadCogs('slashcommands')
if yaml_load(configFile):
@ -279,11 +278,8 @@ if yaml_load(configFile):
if any([x for x in yaml_load(lookupFile).values()]):
loadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py')
loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
loadCog(f'./{cogsDir}/slashcommands/secondary/tcard.py')
loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
if yaml_load(pitchesFile):
loadCog(f'./{cogsDir}/events/secondary/pitch_listener.py')
if any([len(yaml_load(configFile)[x]['membership']) > 0 for x in yaml_load(configFile)]):
loadCog(f'./{cogsDir}/slashcommands/secondary/edit_membership.py')
client.run(os.getenv('BOT_TOKEN'))
client.run(os.getenv('TEST_3_TOKEN'))

View File

@ -0,0 +1,58 @@
import os
from dotenv import load_dotenv # Import OS variables from Dotenv file.
load_dotenv() # Load Dotenv. Delete this for production
import asyncio # Discord Py Dependency
import discord # Main Lib
from discord.ext import commands # Commands module
from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash Command Library
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
from deepdiff import DeepDiff
from pprint import pprint
from bot import loadCog, unloadCog, cogsDir, checkConfig, parseConfigCheck, yaml_load, configFile
##### Control Cog
class Control(commands.Cog, name='Cog Control Commands'):
def __init__(self, client):
self.client = client
#### Check if user is an administrator
async def cog_check(self, ctx:commands.Context):
for role in ctx.author.roles:
if role.permissions.administrator:
return True
return ctx.author.guild_permissions.administrator
@commands.command(
name='debug',
description='Toggles debug feature for the guild. Enter either `on` or `off`.',
brief='Toggle debug features.'
)
async def _debug(self, ctx:commands.Context, toggle:str):
if toggle.lower() == 'on':
loadCog(f'./debug/debug.py')
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.```')
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:commands.Context):
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(Control(client))

View File

@ -16,12 +16,8 @@ class Migrate(commands.Cog, name='Migrate Command'):
self.client = client
#### Permission Check: Only available to the bot's maintainer.
async def cog_check(self, ctx:commands.Context):
conf = yaml_load(configFile)
for role in ctx.author.roles:
if 'maintainer' in conf[str(ctx.guild.id)]['roles']:
if role.id == conf[str(ctx.guild.id)]['roles']['maintainer']: return True
return ctx.author.id == ctx.guild.owner_id
async def cog_check(self, ctx):
return ctx.author.id == int(os.getenv('BOT_MAINTAINER_ID'))
@commands.command(
name='migrate',
@ -73,7 +69,7 @@ class Migrate(commands.Cog, name='Migrate Command'):
await r.edit(
reason=f'`migrate` command issued by {ctx.author.display_name}',
mentionable=True,
colour=discord.Colour.green()
colour=discord.Colour.green
)
c = discord.utils.get(ctx.guild.categories, name=r.name)
if c is None:

View File

@ -20,7 +20,7 @@ class on_guild_join(commands.Cog, name='On Guild Join Events'):
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.```")
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

@ -6,7 +6,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
import logging
# logger and handler
from bot import configFile, yaml_load, yaml_dump
from bot import configFile, yaml_load, yaml_dump, dataFile, lookupFile, gmFile, categoriesFile, unloadCog, cogsDir
##### Actions for the bot to take whenever there is a new role deleted.
@ -17,11 +17,71 @@ class on_guild_role_delete(commands.Cog, name='On Guild Role Delete Events'):
@commands.Cog.listener()
async def on_guild_role_delete(self, role):
conf = yaml_load(configFile)
gms = yaml_load(gmFile)
categories = yaml_load(categoriesFile)
lookup = yaml_load(lookupFile)
data = yaml_load(dataFile)
guildStr = str(role.guild.id)
rStr = str(role.id)
#### 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)]['roles']['admin']:
conf[str(role.guild.id)]['roles']['admin'].remove(role.id)
if role.id in conf[guildStr]['roles']['admin']:
conf[guildStr]['roles']['admin'].remove(role.id)
yaml_dump(conf, configFile)
return
#### 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.
#### Bot will delete membership type if the membership role is deleted.
if role.id in conf[guildStr]['membership']:
conf[guildStr]['membership'].remove(role.id)
yaml_dump(conf, configFile)
if not any([x['membership'] for x in yaml_load(configFile).values()]):
unloadCog(f'./{cogsDir}/slashcommands/secondary/edit_membership.py')
await self.client.slash.sync_all_commands()
return
#### Synchronising with the Game Creation Process
# Whenever a game role is deleted, the Bot will update the guild's configurations and channels to match.
if rStr in lookup[guildStr]:
game_title = lookup[guildStr][rStr]['game_title']
time = lookup[guildStr][rStr]['time']
c = role.guild.get_channel(lookup[guildStr][rStr]['category'])
gm = await role.guild.fetch_member(lookup[guildStr][rStr]['gm'])
del data[guildStr][time][rStr]
channelsFound = False
if c is not None:
channelsFound = True
for t in role.guild.text_channels:
if t.category == c:
await t.delete(reason=f'Role Delete Event Listener: Synchronising with deletion of role `{role.name}`')
for v in role.guild.voice_channels:
if v.category == c:
await v.delete(reason=f'Role Delete Event Listener: Synchronising with deletion of role `{role.name}`')
del categories[guildStr][str(c.id)]
await c.delete(reason=f'Role Delete Event Listener: Synchronising with deletion of role `{role.name}`')
lookup[guildStr].pop(rStr, None)
output = f'The game `{game_title}` for timeslot `{conf[guildStr]["timeslots"][time]}` and with GM `{gm.display_name}` has been deleted.'
if channelsFound:
output = ''.join([output,' All associated text, voice, and category channels have been deleted.'])
else:
output = ''.join([output,' No associated text, voice, or category channels were found. Please delete them manually if they still persist.'])
if 'mod' in conf[guildStr]['channels'] and 'committee' in conf[guildStr]['roles']:
c = discord.utils.find(lambda x: x.id == conf[guildStr]['channels']['mod'], role.guild.channels)
await c.send(
content = f'```{output}```'
)
gms[guildStr][str(gm.id)].remove(role.id)
if not gms[guildStr][str(gm.id)]: del gms[guildStr][str(gm.id)]
if not data[guildStr][time]: del data[guildStr][time]
yaml_dump(lookup, lookupFile)
yaml_dump(data, dataFile)
yaml_dump(gms, gmFile)
yaml_dump(categories, categoriesFile)
if not any([x for x in yaml_load(lookupFile).values()]):
unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py')
if self.client.get_cog('Player Commands') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
if self.client.get_cog('Pitch Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
await self.client.slash.sync_all_commands()
def setup(client):
client.add_cog(on_guild_role_delete(client))

View File

@ -5,8 +5,9 @@ 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
import re
# logger and handler
from bot import configFile, yaml_load, yaml_dump
from bot import configFile, yaml_load, yaml_dump, gmFile, categoriesFile, lookupFile, dataFile
##### Actions for the bot to take whenever there is a new role deleted.
@ -17,18 +18,56 @@ class on_guild_role_update(commands.Cog, name='On Guild Role Update Events'):
@commands.Cog.listener()
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
gms = yaml_load(gmFile)
categories = yaml_load(categoriesFile)
lookup = yaml_load(lookupFile)
data = yaml_load(dataFile)
guildStr = str(after.guild.id)
rStr = str(after.id)
#### If the original role is in the config is 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)]['roles']['admin']:
if after.is_bot_managed() or after.is_integration() or not after.permissions.administrator:
conf[str(after.guild.id)]['roles']['admin'].remove(after.id)
yaml_dump(conf, configFile)
return
#### 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)]['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.
#### If the role is one of the Admin roles and is stripped of admin permissions, updates the bot's config to delete that role, preventing unnecessary roles from accumulating.
return
#### If the original role is a game role:
if rStr in lookup[guildStr]:
#### Suppress invalid changes
revert = {}
# Check name change
if before.name != after.name:
if ': ' not in after.name: revert['name'] = before.name
else:
if after.name.split(': ', maxsplit=1)[0].lower() not in conf[guildStr]['timeslots']: revert['name'] = before.name
# Check role colour
if before.colour != after.colour: revert['colour'] = before.colour
# Check role hoist
if before.hoist != after.hoist: revert['hoist'] = before.hoist
# Check role mentionable
if before.mentionable != after.mentionable: revert['mentionable'] = before.mentionable
# Check role permissions
if before.permissions != after.mentionable: revert['permissions'] = before.permissions
#### Suppress changes if the new settings are invalid
if revert:
revert['reason'] = 'Role Update Event Listener: Suppressing permission change for game role.'
await after.edit(**revert)
return
def setup(client):
client.add_cog(on_guild_role_update(client))

View File

@ -0,0 +1,36 @@
import yaml # YAML parser for Bot config files
import asyncio # Discord Py Dependency
import discord # Main Lib
from discord.ext import commands # Commands module
from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash Command Library
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
import logging
# logger and handler
from bot import configFile, yaml_load, yaml_dump
#### Error Handler Event Listener for Slash Command Errors
class on_slash_command_error(commands.Cog, name='On Command Error'):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_slash_command_error(self, ctx:SlashContext, error):
if isinstance(error, Exception):
await ctx.send(
content='```Invalid Command: {error}```',
tts=True,
hidden=True,
delete_after=10,
)
# if isinstance(error, commands.CommandNotFound):
# print(f'Error: User {ctx.author.name}#{ctx.author.discriminator} / {ctx.author.display_name} entered an invalid command <{ctx.message.clean_content}> in the guild {ctx.guild.name}.')
# await ctx.reply(f'```Error: This is not a valid command.```')
# elif isinstance(error, commands.CheckFailure):
# print(f'Error: User {ctx.author.name}#{ctx.author.discriminator} / {ctx.author.display_name} is not authorised to issue the command <{ctx.command.name}> in the guild {ctx.guild.name}.')
# await ctx.reply(f'```Error: You are not authorised to issue this command.```')
# else:
# print(f'User {ctx.author.name}#{ctx.author.discriminator} / {ctx.author.display_name} received error: "{error}" when attempting to issue command <{ctx.command.name}> in the guild {ctx.guild.name}.')
# await ctx.reply(f'```Error: {error}```')
def setup(client):
client.add_cog(on_slash_command_error(client))

View File

@ -1,201 +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, ComponentContext # Slash Command Library
from discord_slash.utils.manage_commands import create_choice, create_option, create_permission # Slash Command features
from discord_slash.model import SlashCommandPermissionType, ButtonStyle
from discord_slash.client import SlashCommand
from discord_slash.utils.manage_components import create_select, create_select_option, create_actionrow, wait_for_component, create_button, create_actionrow
from bot import configFile, yaml_load, yaml_dump, cogsDir, unloadCog, dataFile, lookupFile, gmFile, categoriesFile, pitchesFile, configFile, dataFile, lookupFile, unloadCog
#### Pitch Command
class PitchListener(commands.Cog, name='Pitch Listener'):
def __init__(self, client):
self.client = client
@commands.Cog.listener(name='on_component')
async def _response_defer(self, ctx:ComponentContext):
pitches = yaml_load(pitchesFile)
guildStr = str(ctx.guild.id)
if not pitches.get(guildStr, {}): return # If no pitches for current guild, ignore.
[timeslot] = [*pitches[guildStr]]
if ctx.origin_message.id not in pitches[guildStr][timeslot]['messages'] + [pitches[guildStr][timeslot]['control']]: return # If the context id is not in the pitch menu, ignore
await ctx.defer(hidden = True)
@commands.Cog.listener(name='on_component')
async def _pitch_listener(self, ctx:ComponentContext):
conf = yaml_load(configFile)
data = yaml_load(dataFile)
lookup = yaml_load(lookupFile)
pitches = yaml_load(pitchesFile)
guildStr = str(ctx.guild.id)
if not pitches.get(guildStr, {}): return # If no pitches for current guild, ignore.
[timeslot] = [*pitches[guildStr]]
if ctx.origin_message.id not in pitches[guildStr][timeslot]['messages'] + [pitches[guildStr][timeslot]['control']]: return # If the context id is not in the pitch menu, ignore
newcomer = returning_player = None
if 'newcomer' in conf[guildStr]['roles']: newcomer = discord.utils.find(lambda x: x.id == conf[guildStr]['roles']['newcomer'], ctx.guild.roles)
if 'returning_player' in conf[guildStr]['roles']: returning_player = discord.utils.find(lambda x: x.id == conf[guildStr]['roles']['returning_player'], ctx.guild.roles)
control = await ctx.channel.fetch_message(pitches[guildStr][timeslot]['control'])
header_message = await ctx.channel.fetch_message(pitches[guildStr][timeslot]['header_message'])
if ctx.origin_message.id == control.id:
if not (set(ctx.author.roles) & set([ctx.guild.get_role(x) for x in conf[str(ctx.guild.id)]['roles']['admin']]) or ctx.author == ctx.guild.owner):
await ctx.send(f'```Error: You are not authorised to do this. The control panel may only be issued by an administrator.```',hidden=True)
else:
if ctx.custom_id == 'allow_returning':
await ctx.channel.set_permissions(reason=f'/pitch control switch triggered by {ctx.author.display_name}', target=returning_player, read_messages=True)
await ctx.send(f'```Returning Players have now been allowed access to the pitch menu.```', hidden=True)
if ctx.custom_id == 'allow_newcomers':
await ctx.channel.set_permissions(reason=f'/pitch control switch triggered by {ctx.author.display_name}', target=newcomer, read_messages=True)
await ctx.send(f'```Newcomers have now been allowed access to the pitch menu.```', hidden=True)
if ctx.custom_id == 'allow_all':
await ctx.channel.set_permissions(reason=f'/pitch control switch triggered by {ctx.author.display_name}', target=ctx.guild.default_role, read_messages= True, send_messages=False)
await ctx.send(f'```All members have now been allowed access to the pitch menu.```', hidden=True)
if ctx.custom_id == 'close_pitches':
await ctx.send(f'```Please wait: closing pitches.```', hidden=True)
await header_message.delete()
for message in pitches[guildStr][timeslot]['messages']:
m = await ctx.channel.fetch_message(message)
await m.delete()
await control.delete()
await ctx.channel.edit(reason=f'/pitch command issued by {ctx.author.display_name}', overwrites={})
await ctx.channel.send('```Pitch menu cleared. Pitches have now concluded.```')
del pitches[guildStr][timeslot]
if not pitches[guildStr]: del pitches[guildStr]
yaml_dump(pitches,pitchesFile)
if not pitches and self.client.get_cog('Pitch Listener') is not None:
unloadCog(f'./{cogsDir}/events/secondary/pitch_listener.py')
#### Deactivate global pitch listener
else:
index = int(ctx.custom_id.split('_',1)[1])
if ctx.custom_id.startswith('join_'):
if set([x.id for x in ctx.author.roles]) & set(pitches[guildStr][timeslot]['roles'].values()):
for r in list(set([x.id for x in ctx.author.roles]) & set(pitches[guildStr][timeslot]['roles'].values())):
role = ctx.guild.get_role(r)
rStr = str(role.id)
if role.id != pitches[guildStr][timeslot]['roles'][index]:
await ctx.author.remove_roles(role,reason=f'/pitch interaction by {ctx.author.display_name}')
i = pitches[guildStr][timeslot]['indices'][role.id]
element = pitches[guildStr][timeslot]['entries'][i]
gm = await self.client.fetch_user(element['gm'])
if ctx.author.id != lookup[guildStr][rStr]['gm']:
data[guildStr][timeslot][rStr]['current_players'] -= 1
element['current_players'] -= 1
o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n'
if element['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n'])
if element['min_players'] is not None: o = ''.join([o,f'Minimum Players: {str(element["min_players"])} '])
if element['max_players'] is not None: o = ''.join([o,f'Maximum Players: {str(element["max_players"])}\n'])
if element['platform'] is not None: o = ''.join([o,f'Platform: {element["platform"]}\n'])
o = ''.join([o,f'```'])
spaces_remaining = element["max_players"] - element["current_players"]
o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}'])
m = await ctx.channel.fetch_message(pitches[guildStr][timeslot]['messages'][i])
await m.edit(content=o)
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.text_channels)
if tc is None:
c = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['category'],ctx.guild.categories)
if c is not None:
tPos = len(ctx.guild.channels)
for t in c.text_channels:
if t.position <= tPos:
tc = t
tPos = t.position
if tc is not None:
await tc.send(f'```{ctx.author.display_name} has left the game.```')
role = ctx.guild.get_role(pitches[guildStr][timeslot]['roles'][index])
rStr = str(role.id)
if role in ctx.author.roles:
await ctx.send(f'```Error: You are already in the game `{lookup[guildStr][rStr]["game_title"]}`.```', hidden=True)
else:
await ctx.author.add_roles(role,reason=f'/pitch interaction by {ctx.author.display_name}')
element = pitches[guildStr][timeslot]['entries'][index]
if ctx.author.id != lookup[guildStr][rStr]['gm']:
data[guildStr][timeslot][rStr]['current_players'] += 1
element['current_players'] += 1
gm = await self.client.fetch_user(element['gm'])
o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n'
if element['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n'])
if element['min_players'] is not None: o = ''.join([o,f'Minimum Players: {str(element["min_players"])} '])
if element['max_players'] is not None: o = ''.join([o,f'Maximum Players: {str(element["max_players"])}\n'])
if element['platform'] is not None: o = ''.join([o,f'Platform: {element["platform"]}\n'])
o = ''.join([o,f'```'])
spaces_remaining = element["max_players"] - element["current_players"]
o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}'])
m = await ctx.channel.fetch_message(pitches[guildStr][timeslot]['messages'][index])
await m.edit(content=o)
await ctx.send(f'You have joined the game `{lookup[guildStr][rStr]["game_title"]}`.',hidden=True)
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.text_channels)
if tc is None:
c = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['category'],ctx.guild.categories)
if c is not None:
tPos = len(ctx.guild.channels)
for t in c.text_channels:
if t.position <= tPos:
tc = t
tPos = t.position
if tc is not None:
await tc.send(f'```{ctx.author.display_name} has joined the game.```')
ts = lookup[guildStr][rStr]['time']
p = await tc.pins()
if p is not None:
header = discord.utils.find(lambda x: x.id == data[guildStr][ts][rStr]['header_message'], p)
if header is not None:
text = header.content.split('\n')
for line, item in enumerate(text):
if 'Current Players: ' in item:
text[line] = f'Current Players: {str(data[guildStr][ts][rStr]["current_players"]) if data[guildStr][ts][rStr]["current_players"] is not None else str(0)}'
break
await header.edit(content='\n'.join(text))
elif ctx.custom_id.startswith('leave_'):
role = ctx.guild.get_role(pitches[guildStr][timeslot]['roles'][index])
rStr = str(role.id)
if role not in ctx.author.roles:
await ctx.send(f'```Error: You are not in the game `{lookup[guildStr][rStr]["game_title"]}`.```', hidden=True)
else:
await ctx.author.remove_roles(role,reason=f'/pitch interaction by {ctx.author.display_name}')
element = pitches[guildStr][timeslot]['entries'][index]
if ctx.author.id != lookup[guildStr][rStr]['gm']:
data[guildStr][timeslot][rStr]['current_players'] -= 1
element['current_players'] -= 1
gm = await self.client.fetch_user(element['gm'])
o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n'
if element['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n'])
if element['min_players'] is not None: o = ''.join([o,f'Minimum Players: {str(element["min_players"])} '])
if element['max_players'] is not None: o = ''.join([o,f'Maximum Players: {str(element["max_players"])}\n'])
if element['platform'] is not None: o = ''.join([o,f'Platform: {element["platform"]}\n'])
o = ''.join([o,f'```'])
spaces_remaining = element["max_players"] - element["current_players"]
o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}'])
me = await ctx.channel.fetch_message(pitches[guildStr][timeslot]['messages'][index])
await me.edit(content=o)
await ctx.send(f'You have left the game `{lookup[guildStr][rStr]["game_title"]}`.',hidden=True)
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.text_channels)
if tc is None:
c = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['category'],ctx.guild.categories)
if c is not None:
tPos = len(ctx.guild.channels)
for t in c.text_channels:
if t.position <= tPos:
tc = t
tPos = t.position
if tc is not None:
await tc.send(f'```{ctx.author.display_name} has left the game.```')
ts = lookup[guildStr][rStr]['time']
p = await tc.pins()
if p is not None:
header = discord.utils.find(lambda x: x.id == data[guildStr][ts][rStr]['header_message'], p)
if header is not None:
text = header.content.split('\n')
for line, item in enumerate(text):
if 'Current Players: ' in item:
text[line] = f'Current Players: {str(data[guildStr][ts][rStr]["current_players"]) if data[guildStr][ts][rStr]["current_players"] is not None else str(0)}'
break
await header.edit(content='\n'.join(text))
yaml_dump(data, dataFile)
yaml_dump(pitches, pitchesFile)
def setup(client):
client.add_cog(PitchListener(client))

View File

@ -3,9 +3,8 @@ 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, ComponentContext # Slash Command Library
from discord_slash.utils.manage_components import create_select, create_select_option, create_actionrow, wait_for_component, create_button, create_actionrow
from discord_slash.utils.manage_commands import create_choice, create_option
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
@ -17,16 +16,15 @@ class MemberVerification(commands.Cog, name='Member Verification Cog'):
def __init__(self, client):
self.client = client
@commands.Cog.listener(name='on_message')
async def _submission_listener(self, message):
@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 None: return
if message.author.bot: return
if conf[guildStr]['channels'].get('signup', None) is not None: return
if message.channel.id != conf[guildStr]['channels']['signup']: return
if not (message.attachments):
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
@ -34,12 +32,12 @@ class MemberVerification(commands.Cog, name='Member Verification Cog'):
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='Review', emoji='⚠️', custom_id=f'review_{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 by `{message.author.display_name}`.```'
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 = ''.join((admins,o))
o = '\n'.join((o,admins))
m = await message.reply(
content= o,
components=[
@ -57,70 +55,55 @@ class MemberVerification(commands.Cog, name='Member Verification Cog'):
)
]
)
if conf[guildStr]['notifications'].get('signup', False):
embed = discord.Embed(
title = f'Member Verification Request',
description = f'User: {message.author.name}\n\n[Jup to Message]({m.jump_url})',
colour = discord.Colour.blue(),
)
if conf[guildStr]['channels'].get('mod', None) is not None:
await message.guild.get_channel(conf[guildStr]['channels']['mod']).send(f'```New membership verification request.```\n{admins}', embed=embed)
@commands.Cog.listener(name='on_component')
async def _verification_response(self, ctx:ComponentContext):
conf = yaml_load(configFile)
categories = yaml_load(categoriesFile)
guildStr = str(ctx.guild.id)
admins = '|'.join([discord.utils.get(ctx.guild.roles, id=x).mention for x in conf[guildStr]['roles']['admin']])
lookup = yaml_load(lookupFile)
if ctx.channel.id != conf[guildStr]['channels']['signup']: return
if not (set(ctx.author.roles) & set([ctx.guild.get_role(x) for x in conf[str(ctx.guild.id)]['roles']['admin']]) or ctx.author == ctx.guild.owner):
await ctx.send(f'```Error: You are not authorised to assign memberships for guild `{ctx.guild.name}`. Only administrators may assign memberships using this interface.```', hidden=True)
else:
submission = await ctx.channel.fetch_message(int(ctx.custom_id.split('_',1)[1]))
if ctx.custom_id.startswith('done_'):
await ctx.send(f'```Membership verification complete.```', hidden=True)
await ctx.origin_message.delete()
elif ctx.custom_id.startswith('deny_'):
await 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)
await ctx.origin_message.delete()
elif ctx.custom_id.startswith('review_'):
await ctx.send(f'```Membership review requested.```', 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 ctx.custom_id.startswith('student_'):
await 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 `{ctx.author.display_name}`.')
await submission.author.send(f'```You have additionally been assigned the role `Student` in the guild `{submission.guild.name}`.```')
elif ctx.custom_id.startswith('membership_'):
[selected_membership] = ctx.selected_options
selected_role = ctx.guild.get_role(int(selected_membership))
if selected_role not in submission.author.roles:
await 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 `{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 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 `{ctx.author.display_name}`.')
await submission.author.send(f'```Your role `{selected_role.name}` has been removed in the guild `{submission.guild.name}`.```')
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:
pass
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

@ -15,16 +15,15 @@ class RestrictionListener(commands.Cog, name='Membership Restriction Listener'):
self.client = client
# Block non-verified user from posting messages.
@commands.Cog.listener(name='on_message')
async def _restriction_listener(self,message):
@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 not conf[guildStr].get('restrict',False): return
if conf[guildStr].get('restrict',False): return
if message.author.bot: return
if message.channel.category is None: return
if str(message.channel.category.id) not in categories[guildStr]: 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
@ -34,21 +33,18 @@ class RestrictionListener(commands.Cog, name='Membership Restriction Listener'):
await message.delete()
# Reinstate on verification
@commands.Cog.listener(name='on_member_update')
async def _reinstate_listener(self, before, after):
@commands.Cog.listener()
async def on_member_update(self, before, after):
if before.roles == after.roles: return
if len(set(after.roles) - set(before.roles)) != 1: return
[d] = list(set(after.roles) - set(before.roles))
conf = yaml_load(configFile)
categories = yaml_load(categoriesFile)
guildStr = str(after.guild.id)
if d.id not in conf[guildStr]['membership']: return
lookup = yaml_load(lookupFile)
if not set(after.roles) & set([after.guild.get_role(x) for x in conf[guildStr]['membership']]): return
for game in list(set(after.roles) & set([after.guild.get_role(int(x)) for x in lookup[guildStr]])):
c = discord.utils.get(after.guild.categories, id=lookup[guildStr][str(game.id)]['category'])
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 = None, reason= f'Membership Restriction: {after.display_name} has been verified and reinstated.')
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

@ -59,10 +59,6 @@ class Configuration(commands.Cog, name='Configuration Commands'):
create_choice(
name='`Student` role',
value='student'
),
create_choice(
name='`Bot Maintainer` role',
value='maintainer'
)
]
),
@ -101,7 +97,7 @@ class Configuration(commands.Cog, name='Configuration Commands'):
permissions=discord.Permissions(administrator=True) if key == 'committee' else discord.Permissions().none(),
reason=f'`/config roles` command issued by {ctx.author.display_name}',
colour = discord.Colour.orange() if key == 'bot' else discord.Colour.blue() if key == 'committee' else discord.Colour.default(),
hoist=True if key == 'committee' or key == 'bot' else None,
hoist=True if key == 'committee' else None,
)
conf = yaml_load(configFile)
guildStr = str(ctx.guild.id)
@ -123,13 +119,10 @@ class Configuration(commands.Cog, name='Configuration Commands'):
if self.client.get_cog('Player Commands') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
flag = True
if self.client.get_cog('T-Card Command') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/tcard.py')
flag = True
if self.client.get_cog('Pitch Command') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
flag = True
if flag: await self.client.slash.sync_all_commands()
if flag: await self.client.slash.sync_all_commands()
@cog_ext.cog_subcommand(
base='config',
@ -137,7 +130,7 @@ class Configuration(commands.Cog, name='Configuration Commands'):
name='channels',
description='Designate the various key channels for the Bot to interact with.',
# base_description='Commands for configuring the various parameters of the Guild',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Designates the various key Bot channels for the guild.',
guild_ids=guild_ids,
@ -210,7 +203,7 @@ class Configuration(commands.Cog, name='Configuration Commands'):
name='notifications',
description='Configure monitoring and notifications to Committee for member query channels.',
# base_description='Commands for configuring the various parameters of the Guild',
base_default_permission=False,
# 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,
@ -254,7 +247,7 @@ class Configuration(commands.Cog, name='Configuration Commands'):
name='add',
description='Add a timeslot at which the Guild will host games.',
# base_description='Commands for configuring the various parameters of the Guild',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
subcommand_group_description='Manages timeslots available for games on the guild.',
guild_ids=guild_ids,
@ -304,9 +297,6 @@ class Configuration(commands.Cog, name='Configuration Commands'):
if self.client.get_cog('Player Commands') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
Flag = True
if self.client.get_cog('T-Card Command') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/tcard.py')
Flag = True
if self.client.get_cog('Pitch Command') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
Flag = True
@ -318,7 +308,7 @@ class Configuration(commands.Cog, name='Configuration Commands'):
name='add',
description='Add a membership type for the Guild.',
# base_description='Commands for configuring the various parameters of the Guild',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
subcommand_group_description='Manages the different categories of membership available to the Guild.',
guild_ids=guild_ids,
@ -389,7 +379,7 @@ class Configuration(commands.Cog, name='Configuration Commands'):
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_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,

View File

@ -35,7 +35,7 @@ class EditMembership(commands.Cog, name='Edit Membership'):
name='remove',
description='Remove a registered membership role.',
# base_description='Commands for configuring the various parameters of the Guild',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Adds a time slot available to the channel for games.',
guild_ids=guild_ids,
@ -53,13 +53,9 @@ class EditMembership(commands.Cog, name='Edit Membership'):
if 'membership' not in conf[str(ctx.guild.id)]:
conf[str(ctx.guild.id)]['timeslots'] = {}
if role.id in conf[str(ctx.guild.id)]['membership']:
conf[str(ctx.guild.id)]['membership'].remove(role.id)
yaml_dump(conf, configFile)
await ctx.send(f'```Membership type {role.name} has been deleted for the guild `{ctx.guild.name}`.```', hidden=True)
await ctx.send(f'```Membership type {role.name} has been deleted for the guild `{ctx.guild.name}`.```')
await role.delete(reason=f'`/config membership remove` command issued by `{ctx.author.display_name}`.')
if not any([x['membership'] for x in yaml_load(configFile).values()]):
unloadCog(f'./{cogsDir}/slashcommands/secondary/edit_membership.py')
await self.client.slash.sync_all_commands()
#### Bot will delete the membership role. Then the on_guild_role_delete event listener will synchronise the configurations.
elif conf[str(ctx.guild.id)]['membership']:
output = f'Role `{role.name}` is not a registered membership role in the guild `{ctx.guild.name}`. Please select a valid membership role.\n\n Eligible roles are:\n'
for m in conf[str(ctx.guild.id)]['membership']:
@ -74,7 +70,7 @@ class EditMembership(commands.Cog, name='Edit Membership'):
name='list',
description='List the existing game memberships on the server.',
# base_description='Commands for configuring the various parameters of the Guild',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Adds a time slot available to the channel for games.',
guild_ids=guild_ids,

View File

@ -290,9 +290,6 @@ class GameCreate(commands.Cog, name='Game Create'):
if self.client.get_cog('Player Commands') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
flag = True
if self.client.get_cog('T-Card Command') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/tcard.py')
flag = True
if self.client.get_cog('Pitch Command') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
flag = True

View File

@ -27,7 +27,7 @@ class GameManagement(commands.Cog, name='Game Management'):
name='delete',
description='Deletes a game role and accompanying category, text, voice channels, and data.',
# base_description='Commands for setting up and removing games on the Guild.',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Adds a time slot available to the channel for games.',
guild_ids=guild_ids,
@ -60,46 +60,11 @@ class GameManagement(commands.Cog, name='Game Management'):
return
game_title = lookup[guildStr][rStr]['game_title']
time = lookup[guildStr][rStr]['time']
c = ctx.guild.get_channel(lookup[guildStr][rStr]['category'])
gm = lookup[guildStr][rStr]['gm']
channelsFound = False
del data[guildStr][time][rStr]
if c is not None:
channelsFound = True
for t in ctx.guild.text_channels:
if t.category == c:
await t.delete(reason=f'/game delete command issued by `{ctx.author.display_name}`')
for v in ctx.guild.voice_channels:
if v.category == c:
await v.delete(reason=f'/game delete command issued by `{ctx.author.display_name}`')
del categories[guildStr][str(c.id)]
await c.delete(reason=f'/game delete command issued by `{ctx.author.display_name}`')
lookup[guildStr].pop(rStr, None)
gm_m = await ctx.guild.fetch_member(gm)
output = f'The game `{game_title}` for timeslot `{conf[guildStr]["timeslots"][time]}` and with GM `{gm_m.display_name}` has been deleted.'
if channelsFound:
output = ''.join([output,' All associated text, voice, and category channels have been deleted.'])
else:
output = ''.join([output,' No associated text, voice, or category channels were found. Please delete them manually if they still persist.'])
if 'mod' in conf[guildStr]['channels'] and 'committee' in conf[guildStr]['roles']:
c = discord.utils.find(lambda x: x.id == conf[guildStr]['channels']['mod'], ctx.guild.channels)
await c.send(
content = f'```{output}```'
)
gm = await ctx.guild.fetch_member(lookup[guildStr][rStr]['gm'])
output = f'The game `{game_title}` for timeslot `{conf[guildStr]["timeslots"][time]}` and with GM `{gm.display_name}` has been deleted.'
await ctx.send(f'```Game `{game_title}` has been deleted.```', hidden=True)
await game_role.delete(reason=f'/game delete command issued by `{ctx.author.display_name}`')
gms[guildStr][str(gm)].remove(game_role.id)
if not gms[guildStr][str(gm)]: del gms[guildStr][str(gm)]
if not data[guildStr][time]: del data[guildStr][time]
yaml_dump(lookup, lookupFile)
yaml_dump(data, dataFile)
yaml_dump(gms, gmFile)
yaml_dump(categories, categoriesFile)
if not any([x for x in yaml_load(lookupFile).values()]):
unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py')
if self.client.get_cog('Player Commands') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
if self.client.get_cog('T-Card Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/tcard.py')
if self.client.get_cog('Pitch Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
await self.client.slash.sync_all_commands()
#### Bot will delete the game role. The on_guild_role_delete event listener will then recognise this deletion and synchronise the categories and data files accordingly.
@cog_ext.cog_subcommand(
base='game',
@ -107,7 +72,7 @@ class GameManagement(commands.Cog, name='Game Management'):
name='modify',
description='Edit the information of an existing game channel.',
# base_description='Commands for setting up and removing games on the Guild.',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Adds a time slot available to the channel for games.',
guild_ids=guild_ids,
@ -195,12 +160,9 @@ class GameManagement(commands.Cog, name='Game Management'):
rStr = str(r.id)
old_time = lookup[guildStr][rStr]['time']
time = re.sub(r"\W+",'', timeslot[:9].lower()) if timeslot else old_time
if guildStr not in lookup:
lookup[guildStr] = {}
if guildStr not in data:
data[guildStr] = {}
if time not in data[guildStr]:
data[guildStr][time] = {}
if guildStr not in lookup: lookup[guildStr] = {}
if guildStr not in data: data[guildStr] = {}
if time not in data[guildStr]: data[guildStr][time] = {}
# Command Validation Checks
if rStr not in lookup[guildStr]:
await ctx.send(f'```Error: This is not a valid game role. Please mention a role that is associated with a game.```',hidden=True)
@ -346,7 +308,7 @@ class GameManagement(commands.Cog, name='Game Management'):
result = ''.join(['```',result,f"The game has been configured with space for {max_players} players (with {current_players if current_players else '0'} currently occupied).",'```'])
output = f'```Hello {gm.display_name}! Your game channels for `{game_title}` have been updated.\nYou can ping your players or edit the game settings by interacting with the game role through the Bot commands. Have fun!```\n'
output = f'```Hello {gm.display_name}! Your game channels for `{game_title}` has been updated.\nYou can ping your players or edit the game settings by interacting with the game role through the Bot commands. Have fun!```\n'
output = ''.join([output,f'```Game Title: {game_title}\n'])
output = ''.join([output,f'GM: {gm.display_name}\n'])
output = ''.join([output,f'Time: {conf[guildStr]["timeslots"][time]}\n'])
@ -395,7 +357,7 @@ class GameManagement(commands.Cog, name='Game Management'):
name='purge',
description='Delete all games in a given timeslot.',
# base_description='Commands for setting up and removing games on the Guild.',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Adds a time slot available to the channel for games.',
guild_ids=guild_ids,
@ -414,18 +376,9 @@ class GameManagement(commands.Cog, name='Game Management'):
async def purgeGames(ctx:SlashContext, timeslot:str):
for g in list(data[guildStr][timeslot].values()):
c = discord.utils.find(lambda x: x.id == g['category'], ctx.guild.categories)
r = discord.utils.find(lambda x: x.id == g['role'], ctx.guild.roles)
for x in c.channels:
await x.delete(reason=f'/game purge command issued by `{ctx.author.display_name}`')
del categories[guildStr][str(c.id)]
await c.delete(reason=f'/game purge command issued by `{ctx.author.display_name}`')
await r.delete(reason=f'/game purge command issued by `{ctx.author.display_name}`')
gms[guildStr][str(g['gm'])].remove(r.id)
if not gms[guildStr][str(g['gm'])]:
del gms[guildStr][str(g['gm'])]
del lookup[guildStr][str(r.id)]
del data[guildStr][timeslot]
#### Bot will delete the game role. The on_guild_role_delete event listener will then recognise this deletion and synchronise the categories and data files accordingly.
if 'timeslots' not in conf[guildStr]: conf[guildStr]['timeslots'] = {}
tsDict = {k: conf[guildStr]['timeslots'][k] for k in data[guildStr] if data[guildStr][k]}
@ -547,16 +500,6 @@ class GameManagement(commands.Cog, name='Game Management'):
await c.send(
content = f'```All games for time slot `{conf[guildStr]["timeslots"][timeslot]}` have been purged.```'
)
yaml_dump(gms,gmFile)
yaml_dump(lookup,lookupFile)
yaml_dump(data,dataFile)
yaml_dump(categories,categoriesFile)
if not any([x for x in yaml_load(lookupFile).values()]):
unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py')
if self.client.get_cog('Player Commands') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
if self.client.get_cog('T-Card Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/tcard.py')
if self.client.get_cog('Pitch Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
await self.client.slash.sync_all_commands()
def setup(client):
client.add_cog(GameManagement(client))

View File

@ -37,7 +37,7 @@ class ManipulateTimeslots(commands.Cog, name='Manipulate Timeslots'):
name='remove',
description='Remove a configured game timeslot.',
# base_description='Commands for configuring the various parameters of the Guild',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Adds a time slot available to the channel for games.',
guild_ids=guild_ids,
@ -91,11 +91,14 @@ class ManipulateTimeslots(commands.Cog, name='Manipulate Timeslots'):
yaml_dump(conf, configFile)
if not any([x['timeslots'] for x in yaml_load(configFile).values()]):
unloadCog(f'./{cogsDir}/slashcommands/secondary/manipulate_timeslots.py')
if self.client.get_cog('Game Management') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py')
if self.client.get_cog('Game Create') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/game_create.py')
if self.client.get_cog('Player Commands') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
if self.client.get_cog('T-Card Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/tcard.py')
if self.client.get_cog('Pitch Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
if self.client.get_cog('Game Management') is not None:
unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py')
if self.client.get_cog('Game Create') is not None:
unloadCog(f'./{cogsDir}/slashcommands/secondary/game_create.py')
if self.client.get_cog('Player Commands') is not None:
unloadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
if self.client.get_cog('Pitch Command') is not None:
unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
await self.client.slash.sync_all_commands()
else:
await ctx.send('```Error: You cannot delete a timeslot that has existing game entries. Please delete all games first.```',hidden=True)
@ -106,7 +109,7 @@ class ManipulateTimeslots(commands.Cog, name='Manipulate Timeslots'):
name='modify',
description='Modify the value of a configured gametime slot.',
# base_description='Commands for configuring the various parameters of the Guild',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Adds a time slot available to the channel for games.',
guild_ids=guild_ids,
@ -148,7 +151,7 @@ class ManipulateTimeslots(commands.Cog, name='Manipulate Timeslots'):
name='list',
description='List the existing game timeslots on the server.',
# base_description='Commands for configuring the various parameters of the Guild',
base_default_permission=False,
# base_default_permission=False,
# base_permissions=permissions,
# subcommand_group_description='Adds a time slot available to the channel for games.',
guild_ids=guild_ids,

View File

@ -9,7 +9,7 @@ from discord_slash.model import SlashCommandPermissionType, ButtonStyle
from discord_slash.client import SlashCommand
from discord_slash.utils.manage_components import create_select, create_select_option, create_actionrow, wait_for_component, create_button, create_actionrow
from bot import configFile, yaml_load, yaml_dump, cogsDir, unloadCog, dataFile, lookupFile, gmFile, categoriesFile, pitchesFile, loadCog
from bot import configFile, yaml_load, yaml_dump, cogsDir, unloadCog, dataFile, lookupFile, gmFile, categoriesFile
#### Pitch Command
@ -30,7 +30,7 @@ class Pitch(commands.Cog, name='Pitch Command'):
@cog_ext.cog_slash(
name='pitch',
description='Initialises the menu for game pitches.',
description='Designates the various key roles referenced by the Bot.',
default_permission=False,
permissions=permissions,
guild_ids=guild_ids,
@ -42,12 +42,8 @@ class Pitch(commands.Cog, name='Pitch Command'):
lookup = yaml_load(lookupFile)
gms = yaml_load(gmFile)
categories = yaml_load(categoriesFile)
pitches = {}
guildStr = str(ctx.guild.id)
pitches = yaml_load(pitchesFile)
if guildStr not in pitches: pitches[guildStr] = {}
if pitches[guildStr]:
await ctx.send(f'```Error: pitches are already running for the guild `{ctx.guild.name}`. Please close the existing pitches first before issuing this command.```')
return
if 'timeslots' not in conf[guildStr]: conf[guildStr]['timeslots'] = {}
tsDict = {k: conf[guildStr]['timeslots'][k] for k in data[guildStr] if data[guildStr][k]}
optionsList = [create_select_option(label=tsDict[x], value=x, description=x) for x in tsDict]
@ -87,14 +83,13 @@ class Pitch(commands.Cog, name='Pitch Command'):
ctx.guild.default_role: p
}
)
if guildStr not in pitches: pitches[guildStr] = {}
if timeslot not in pitches[guildStr]: pitches[guildStr][timeslot] = {}
pitches[guildStr][timeslot]['indices'] = {}
pitches[guildStr][timeslot]['entries'] = [x for x in data[guildStr][timeslot].values()]
pitches[guildStr][timeslot]['entries'].sort(key= lambda x: x['game_title'])
header_message = await ctx.channel.send(
content=f'**Game listing for {conf[guildStr]["timeslots"][timeslot]}**\n_ _```The following are the games that are being pitched. Please select which game you would like to join by clicking on the `Join` button below.```'
)
pitches[guildStr][timeslot]['header_message'] = header_message.id
pitches[guildStr][timeslot]['messages'] = []
pitches[guildStr][timeslot]['roles'] = {}
for index, element in enumerate(pitches[guildStr][timeslot]['entries']):
@ -126,10 +121,8 @@ class Pitch(commands.Cog, name='Pitch Command'):
)
]
)
pitches[guildStr][timeslot]['messages'].append(m.id)
r = discord.utils.find(lambda x: x.id == element['role'],ctx.guild.roles)
pitches[guildStr][timeslot]['roles'][index] = r.id
pitches[guildStr][timeslot]['indices'][r.id] = index
pitches[guildStr][timeslot]['messages'].append(m)
pitches[guildStr][timeslot]['roles'][index] = discord.utils.find(lambda x: x.id == element['role'],ctx.guild.roles)
newcomer = returning_player = None
if 'newcomer' in conf[guildStr]['roles']: newcomer = discord.utils.find(lambda x: x.id == conf[guildStr]['roles']['newcomer'], ctx.guild.roles)
if 'returning_player' in conf[guildStr]['roles']: returning_player = discord.utils.find(lambda x: x.id == conf[guildStr]['roles']['returning_player'], ctx.guild.roles)
@ -176,11 +169,120 @@ class Pitch(commands.Cog, name='Pitch Command'):
)
]
)
pitches[guildStr][timeslot]['control'] = control.id
yaml_dump(pitches,pitchesFile)
if self.client.get_cog('Pitch Listener') is None:
loadCog(f'./{cogsDir}/events/secondary/pitch_listener.py')
#### Activate global pitch listener
while True:
button_ctx = await wait_for_component(
self.client,
messages=pitches[guildStr][timeslot]['messages'] + [control]
)
if button_ctx.origin_message.id == control.id:
if not (set(ctx.author.roles) & set([ctx.guild.get_role(x) for x in conf[str(ctx.guild.id)]['roles']['admin']]) or ctx.author == ctx.guild.owner):
await button_ctx.send(f'```Error: You are not authorised to do this. The control panel may only be issued by an administrator.```',hidden=True)
else:
if button_ctx.custom_id == 'allow_returning':
await ctx.channel.set_permissions(reason=f'/pitch command issued by {ctx.author.display_name}', target=returning_player, read_messages=True)
await button_ctx.send(f'```Returning Players have now been allowed access to the pitch menu.```', hidden=True)
if button_ctx.custom_id == 'allow_newcomers':
await ctx.channel.set_permissions(reason=f'/pitch command issued by {ctx.author.display_name}', target=newcomer, read_messages=True)
await button_ctx.send(f'```Newcomers have now been allowed access to the pitch menu.```', hidden=True)
if button_ctx.custom_id == 'allow_all':
await ctx.channel.set_permissions(reason=f'/pitch command issued by {ctx.author.display_name}', target=ctx.guild.default_role, read_messages= True, send_messages=False)
await button_ctx.send(f'```All members have now been allowed access to the pitch menu.```', hidden=True)
if button_ctx.custom_id == 'close_pitches': break
else:
index = int(button_ctx.custom_id.split('_',1)[1])
if button_ctx.custom_id.startswith('join_'):
if set(button_ctx.author.roles) & set(pitches[guildStr][timeslot]['roles']):
for role in list(set(button_ctx.author.roles) & set(pitches[guildStr][timeslot]['roles'])):
if role != pitches[guildStr][timeslot]['roles'][index]:
await button_ctx.author.remove_roles(role,reason=f'/pitch interaction by {button_ctx.author.display_name}')
data[guildStr][timeslot][str(role.id)]['current_players'] -= 1
element = pitches[guildStr][timeslot]['entries'][index]
gm = await self.client.fetch_user(element['gm'])
o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n'
if element['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n'])
if element['min_players'] is not None: o = ''.join([o,f'Minimum Players: {str(element["min_players"])} '])
if element['max_players'] is not None: o = ''.join([o,f'Maximum Players: {str(element["max_players"])}\n'])
if element['platform'] is not None: o = ''.join([o,f'Platform: {element["platform"]}\n'])
o = ''.join([o,f'```'])
spaces_remaining = element["max_players"] - element["current_players"]
o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}'])
await pitches[guildStr][timeslot]['messages'][index].edit(content=o)
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['text_channel'],ctx.guild.text_channels)
if tc is None:
c = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['category'],ctx.guild.categories)
if c is not None:
tPos = len(ctx.guild.channels)
for t in c.text_channels:
if t.position <= tPos:
tc = t
tPos = t.position
if tc is not None:
await tc.send(f'```{button_ctx.author.display_name} has left the game.```')
role = pitches[guildStr][timeslot]['roles'][index]
if role in button_ctx.author.roles:
await button_ctx.send(f'```Error: You are already in the game `{lookup[guildStr][str(role.id)]["game_title"]}`.```', hidden=True)
else:
await button_ctx.author.add_roles(role,reason=f'/pitch interaction by {button_ctx.author.display_name}')
data[guildStr][timeslot][str(role.id)]['current_players'] += 1
element = pitches[guildStr][timeslot]['entries'][index]
gm = await self.client.fetch_user(element['gm'])
o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n'
if element['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n'])
if element['min_players'] is not None: o = ''.join([o,f'Minimum Players: {str(element["min_players"])} '])
if element['max_players'] is not None: o = ''.join([o,f'Maximum Players: {str(element["max_players"])}\n'])
if element['platform'] is not None: o = ''.join([o,f'Platform: {element["platform"]}\n'])
o = ''.join([o,f'```'])
spaces_remaining = element["max_players"] - element["current_players"]
o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}'])
await pitches[guildStr][timeslot]['messages'][index].edit(content=o)
await button_ctx.send(f'You have joined the game `{lookup[guildStr][str(role.id)]["game_title"]}`.',hidden=True)
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['text_channel'],ctx.guild.text_channels)
if tc is None:
c = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['category'],ctx.guild.categories)
if c is not None:
tPos = len(ctx.guild.channels)
for t in c.text_channels:
if t.position <= tPos:
tc = t
tPos = t.position
if tc is not None:
await tc.send(f'```{button_ctx.author.display_name} has joined the game.```')
elif button_ctx.custom_id.startswith('leave_'):
role = pitches[guildStr][timeslot]['roles'][index]
if role not in button_ctx.author.roles:
await button_ctx.send(f'```Error: You are not in the game `{lookup[guildStr][str(role.id)]["game_title"]}`.```', hidden=True)
else:
await button_ctx.author.remove_roles(role,reason=f'/pitch interaction by {button_ctx.author.display_name}')
data[guildStr][timeslot][str(role.id)]['current_players'] -= 1
element = pitches[guildStr][timeslot]['entries'][index]
gm = await self.client.fetch_user(element['gm'])
o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n'
if element['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n'])
if element['min_players'] is not None: o = ''.join([o,f'Minimum Players: {str(element["min_players"])} '])
if element['max_players'] is not None: o = ''.join([o,f'Maximum Players: {str(element["max_players"])}\n'])
if element['platform'] is not None: o = ''.join([o,f'Platform: {element["platform"]}\n'])
o = ''.join([o,f'```'])
spaces_remaining = element["max_players"] - element["current_players"]
o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}'])
await pitches[guildStr][timeslot]['messages'][index].edit(content=o)
await button_ctx.send(f'You have left the game `{lookup[guildStr][str(role.id)]["game_title"]}`.',hidden=True)
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['text_channel'],ctx.guild.text_channels)
if tc is None:
c = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['category'],ctx.guild.categories)
if c is not None:
tPos = len(ctx.guild.channels)
for t in c.text_channels:
if t.position <= tPos:
tc = t
tPos = t.position
if tc is not None:
await tc.send(f'```{button_ctx.author.display_name} has left the game.```')
yaml_dump(data, dataFile)
await header_message.delete()
for message in pitches[guildStr][timeslot]['messages']: await message.delete()
await control.delete()
await ctx.channel.edit(reason=f'/pitch command issued by {ctx.author.display_name}', overwrites={})
await button_ctx.send('```Pitch menu cleared. Pitches have now concluded.```')
def setup(client):
client.add_cog(Pitch(client))

View File

@ -61,6 +61,9 @@ class PlayerCommands(commands.Cog, name='Player Commands'):
await ctx.send(f'```Error: This is not a valid game role. Please mention a role that is associated with a game.```',hidden=True)
return
if not (set(ctx.author.roles) & set([ctx.guild.get_role(x) for x in conf[str(ctx.guild.id)]['roles']['admin']]) or ctx.author == ctx.guild.owner):
if not set(ctx.author.roles) & set([ctx.guild.get_role(int(x)) for x in gms[str(ctx.guild.id)] if gms[str(ctx.guild.id)][x]]):
await ctx.send(f'```Error: You are not authorised to issue this command. The command may only be issued by an administrator or by a GM.```',hidden=True)
return
if ctx.author.id != lookup[guildStr][rStr]['gm']:
await ctx.send(f'```Error: You are not authorised to issue this command. A player may only be added to a game by the GM or by an administrator.```',hidden=True)
return
@ -134,6 +137,9 @@ class PlayerCommands(commands.Cog, name='Player Commands'):
game = discord.utils.find(lambda x: x.id == categories[guildStr][str(ctx.channel.category.id)], ctx.guild.roles)
rStr = str(game.id)
if not (set(ctx.author.roles) & set([ctx.guild.get_role(x) for x in conf[str(ctx.guild.id)]['roles']['admin']]) or ctx.author == ctx.guild.owner):
if not set(ctx.author.roles) & set([ctx.guild.get_role(int(x)) for x in gms[str(ctx.guild.id)] if gms[str(ctx.guild.id)][x]]):
await ctx.send(f'```Error: You are not authorised to issue this command. The command may only be issued by an administrator or by a GM.```',hidden=True)
return
if ctx.author.id != lookup[guildStr][rStr]['gm']:
await ctx.send(f'```Error: You are not authorised to issue this command. A player may only be added to a game by the GM or by an administrator.```',hidden=True)
return
@ -142,13 +148,13 @@ class PlayerCommands(commands.Cog, name='Player Commands'):
return
await player.remove_roles(game, reason=f'`/player remove` command issued by {ctx.author.display_name}`.')
t = lookup[guildStr][rStr]['time']
if data[guildStr][t][rStr]['current_players'] <= 1: data[guildStr][t][rStr]['current_players'] = 0
if data[guildStr][t][rStr]['current_players'] <= 1: data[guildStr][t][rStr]['current_players'] = None
else: data[guildStr][t][rStr]['current_players'] -= 1
hm = data[guildStr][t][rStr]['header_message']
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.channels)
if tc is not None:
p = await tc.pins()
if p is not None:
if p:
header = discord.utils.find(lambda x: x.id == hm, p)
if header is not None:
text = header.content.split('\n')
@ -200,9 +206,6 @@ class PlayerCommands(commands.Cog, name='Player Commands'):
if game not in player.roles:
await ctx.send(f'```Error: You are not in the game {lookup[guildStr][rStr]["game_title"]}.```',hidden=True)
return
if player.id == lookup[guildStr][rStr]['gm']:
await ctx.send(f'```Error: You are the GM of the game {lookup[guildStr][rStr]["game_title"]} and cannot leave until a new GM is assigned.```',hidden=True)
return
await player.remove_roles(game, reason=f'`/player leave` command issued by {ctx.author.display_name}`.')
t = lookup[guildStr][rStr]['time']
if data[guildStr][t][rStr]['current_players'] <= 1: data[guildStr][t][rStr]['current_players'] = None
@ -211,7 +214,7 @@ class PlayerCommands(commands.Cog, name='Player Commands'):
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.channels)
if tc is not None:
p = await tc.pins()
if p is not None:
if p:
header = discord.utils.find(lambda x: x.id == hm, p)
if header is not None:
text = header.content.split('\n')

View File

@ -1,83 +0,0 @@
import os # OS Locations
import yaml # YAML parser for Bot config files
import asyncio # Discord Py Dependency
import discord # Main Lib
from datetime import datetime
import time
from discord.ext import commands # Commands module
from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash Command Library
from discord_slash.utils.manage_commands import create_choice, create_option, create_permission # Slash Command features
from discord_slash.model import SlashCommandPermissionType
from bot import configFile, yaml_load, yaml_dump, lookupFile, dataFile, gmFile, categoriesFile
#### T Card Command
class TCardCommand(commands.Cog, name='T-Card Command'):
def __init__(self, client):
self.client = client
conf = yaml_load(configFile)
lookup = yaml_load(lookupFile)
data = yaml_load(dataFile)
gms = yaml_load(gmFile)
categories = yaml_load(categoriesFile)
guild_ids= [ int(x) for x in list(lookup)]
@cog_ext.cog_slash(
name='tcard',
description='Invokes a T-Card in the current game.',
default_permission=True,
guild_ids=guild_ids,
)
async def _tcard(self, ctx:SlashContext):
conf = yaml_load(configFile)
lookup = yaml_load(lookupFile)
data = yaml_load(dataFile)
gms = yaml_load(gmFile)
categories = yaml_load(categoriesFile)
guildStr = str(ctx.guild.id)
embed = discord.Embed(
title='T-Card',
description='A T-Card Has Been Played',
colour=discord.Color.dark_red(),
)
embed.set_footer(text=datetime.now().strftime('%a %-d %b %y, %-I:%M %p'))
embed.set_image(url='http://geas.org.uk/wp-content/uploads/2020/08/tcard-1-e1597167776966.png')
"""If this was called in a game channel"""
if str(ctx.channel.category.id) in categories[guildStr]:
"""Send a T-Card graphic to the channel, tagging the role."""
role = ctx.guild.get_role(categories[guildStr][str(ctx.channel.category.id)])
await ctx.channel.send(f'{role.mention}', embed=embed)
if ctx.author_id == lookup[guildStr][str(role.id)]["gm"]:
"""Behaviour for when the GM issues T-Card command."""
await ctx.send(content=f'```You have invoked the T-Card in the game {lookup[guildStr][str(role.id)]["game_title"]} as the GM.```', hidden=True)
else:
"""Default behaviour for when someone who is not the GM issues the command."""
"""Privately message the GM."""
gm = await ctx.guild.fetch_member(lookup[guildStr][str(role.id)]["gm"])
await gm.send(f'**Important**\n```Player {ctx.author.display_name} has invoked a T-Card in your game {lookup[guildStr][str(role.id)]["game_title"]}. Please check in with them and make sure everything is okay. If you need any further help, please feel free to contact a member of the Committee.```')
"""Notify the issuer of the command privately via hidden reply."""
await ctx.send(content=f'```You have invoked the T-Card in the game {lookup[guildStr][str(role.id)]["game_title"]}. The GM has been notified privately.```', hidden=True)
"""Do the audio thing."""
opus = discord.opus.load_opus('/usr/lib/x86_64-linux-gnu/libopus.so.0')
for vc in ctx.channel.category.voice_channels:
v = await vc.connect()
tcardaudio = discord.PCMAudio(open("./assets/tcard.wav", "rb"))
v.play(tcardaudio)
while v.is_playing(): time.sleep(.1)
await v.disconnect()
else:
"""Send a T-Card to the immediate channel if this is a generic channel."""
await ctx.channel.send(embed=embed)
await ctx.send(content=f'```You have invoked the T-Card in the channel {ctx.channel.name}.```', hidden=True)
def setup(client):
client.add_cog(TCardCommand(client))

5
app/data/categories.yml Normal file
View File

@ -0,0 +1,5 @@
'864651943820525609':
'868432993251885067': 868432992165560360
'868436813860184095': 868436812148908112
'868437806064750602': 868437804575756288
'868437891397849119': 868437889799823441

25
app/data/config.yml Normal file
View File

@ -0,0 +1,25 @@
'864651943820525609':
channels:
help: 866645822472454206
mod: 865348933022515220
signup: 866110421592965171
configured: true
membership:
- 866795009121714207
name: Test
notifications:
help: true
signup: true
owner: 493694762210033664
prefix: '-'
roles:
admin:
- 866642278529368095
bot: 866639184121954305
committee: 866642278529368095
newcomer: 866645308091138060
returning_player: 866645365524660224
student: 866645394699714570
timeslots:
avatar: Avatar Time
shera: She Ra Time

1
app/data/data.yml Normal file
View File

@ -0,0 +1 @@
'864651943820525609': {}

1
app/data/gm.yml Normal file
View File

@ -0,0 +1 @@
'864651943820525609': {}

1
app/data/lookup.yml Normal file
View File

@ -0,0 +1 @@
'864651943820525609': {}

View File

@ -8,7 +8,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
from pprint import pprint
from bot import clearConfig, configFile, checkConfig, loadCog, loadCogs, setConfig, unloadCog, unloadCogs, yaml_dump, yaml_load, reloadCog, reloadCogs, pitchesFile, cogsDir, parseConfigCheck
from bot import clearConfig, configFile, loadCog, loadCogs, setConfig, unloadCog, unloadCogs, yaml_dump, yaml_load, reloadCog, reloadCogs
##### Debug Cog
class Debug(commands.Cog, name='Debug Commands'):
@ -16,28 +16,8 @@ class Debug(commands.Cog, name='Debug Commands'):
self.client = client
#### Permission Check: Only available to the bot's maintainer.
async def cog_check(self, ctx:commands.Context):
conf = yaml_load(configFile)
for role in ctx.author.roles:
if 'maintainer' in conf[str(ctx.guild.id)]['roles']:
if role.id == conf[str(ctx.guild.id)]['roles']['maintainer']: return True
return ctx.author.id == ctx.guild.owner_id
@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:commands.Context):
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.```")
async def cog_check(self, ctx):
return ctx.author.id == int(os.getenv('BOT_MAINTAINER_ID'))
@commands.command(
name='reloadcogs',
@ -77,7 +57,7 @@ class Debug(commands.Cog, name='Debug Commands'):
async def _retrievecommands(self, ctx:commands.Context):
c = await utils.manage_commands.get_all_commands(
bot_id=self.client.user.id,
bot_token=os.getenv('BOT_TOKEN'),
bot_token=os.getenv('TEST_3_TOKEN'),
guild_id=ctx.guild.id
)
pprint(c)
@ -93,32 +73,32 @@ class Debug(commands.Cog, name='Debug Commands'):
if command == '--all' or command == '-a':
await utils.manage_commands.remove_all_commands(
bot_id=self.client.user.id,
bot_token=os.getenv('BOT_TOKEN'),
bot_token=os.getenv('TEST_3_TOKEN'),
guild_ids=[ ctx.guild.id ]
)
await ctx.reply(f'```All slash commands have been deleted for the guild `{ctx.guild.name}``.```')
elif command == '--global' or command == '-g':
await utils.manage_commands.remove_all_commands(
bot_id=self.client.user.id,
bot_token=os.getenv('BOT_TOKEN'),
bot_token=os.getenv('TEST_3_TOKEN'),
guild_ids=None
)
await utils.manage_commands.remove_all_commands(
bot_id=self.client.user.id,
bot_token=os.getenv('BOT_TOKEN'),
bot_token=os.getenv('TEST_3_TOKEN'),
guild_ids=[ int(g) for g in yaml_load(configFile)]
)
await ctx.reply('```All slash commands have been deleted globally.```')
else:
c = await utils.manage_commands.get_all_commands(
bot_id=self.client.user.id,
bot_token=os.getenv('BOT_TOKEN'),
bot_token=os.getenv('TEST_3_TOKEN'),
guild_id=ctx.guild.id
)
target = list(filter(lambda t: t['name'] == command, c))[0]['id']
await utils.manage_commands.remove_slash_command(
bot_id=self.client.user.id,
bot_token=os.getenv('BOT_TOKEN'),
bot_token=os.getenv('TEST_3_TOKEN'),
guild_id=ctx.guild.id,
cmd_id=target
)
@ -133,7 +113,7 @@ class Debug(commands.Cog, name='Debug Commands'):
async def _addCommand(self, ctx:commands.Context, command:str, key:str=''):
await utils.manage_commands.add_slash_command(
bot_id=self.client.user.id,
bot_token=os.getenv('BOT_TOKEN'),
bot_token=os.getenv('TEST_3_TOKEN'),
guild_id= None if key == '--global' or key == '-g' else ctx.guild.id,
cmd_name=command,
description='No Description'
@ -172,17 +152,5 @@ class Debug(commands.Cog, name='Debug Commands'):
await self.client.slash.sync_all_commands()
await ctx.reply(f'```All slash commands have been synced with the Server.```')
@commands.command(
name='pitchreset',
description='Debug feature that resets the pitches in case of any error. Clears pitch disables Pitch listeners.',
brief='Reset running pitches.',
aliases=['resetpitches', 'resetpitch']
)
async def _pitchreset(self, ctx:commands.Context):
yaml_dump({}, pitchesFile)
if self.client.get_cog('Pitch Listener') is not None:
unloadCog(f'./{cogsDir}/events/secondary/pitch_listener.py')
await ctx.reply('```Pitches have been hard reset.```')
def setup(client):
client.add_cog(Debug(client))

View File

@ -2,25 +2,20 @@ aiohttp==3.7.4.post0
async-timeout==3.0.1
asyncio==3.4.3
attrs==21.2.0
cffi==1.14.6
chardet==4.0.0
DateTime==4.3
deepdiff==5.5.0
discord==1.7.3
discord-py-slash-command==2.4.0
discord-py-slash-command==2.3.2
discord.py==1.7.3
idna==3.2
multidict==5.1.0
ordered-set==4.0.2
packaging==21.0
pycparser==2.20
PyNaCl==1.4.0
pyparsing==2.4.7
python-dotenv==0.19.0
python-dotenv==0.18.0
pytz==2021.1
PyYAML==5.4.1
six==1.16.0
style==1.1.6
style==1.1.0
typing-extensions==3.10.0.0
update==0.0.1
yarl==1.6.3
zope.interface==5.4.0

View File

@ -1,9 +1,12 @@
version: '3.5'
version: '3.1'
services:
geasbot-app:
build: ./app
container_name: geas_bot
volumes:
- ./app:/usr/src/app
restart: unless-stopped
restart: always
environment:
- BOT_TOKEN=${BOT_TOKEN}
- TEST_TOKEN=${TEST_TOKEN}
- BOT_VERSION=${BOT_VERSION}