22 Commits

Author SHA1 Message Date
4f92e83e48 Documentation and description update
Added .env.example file.
Branch now ready to be merged for the first tagged release of the Bot.
2021-08-05 02:54:52 +01:00
94ce0aa31a Fully implemented /tcard command.
Bug fixes for member signup
Added live update of game header message during pitches
Documentation updates
2021-08-05 02:00:03 +01:00
175a911ed4 Try enabling opus 2021-08-05 00:40:58 +01:00
250ad9b593 removed libiffi 2021-08-05 00:35:00 +01:00
8acdadfc79 Trying to add Opus and dependencies 2021-08-05 00:30:40 +01:00
ca281fb34f Remove audio for the time being 2021-08-04 17:19:47 +01:00
c32cef2da5 Change Libopus name 2021-08-04 17:13:30 +01:00
63146bd042 Trying to install Opus lib via Docker instruction 2021-08-04 17:02:36 +01:00
21d5cba5f5 Trying to get Opus installed 2021-08-04 16:49:56 +01:00
90d6132705 Attempt to load Opus 2021-08-04 16:30:01 +01:00
a10ed8ef29 Let's try if this fixes it 2021-08-04 16:25:48 +01:00
330426b2d3 Change sound file 2021-08-04 16:05:31 +01:00
cc0b3c6bb9 Getting better? 2021-08-04 15:24:39 +01:00
dcf0fec7ac Hope this fixes it 2021-08-04 15:17:13 +01:00
e47e08a272 What the hell is going on!? 2021-08-04 15:11:34 +01:00
497441d841 Somehow this has made everything worse 2021-08-04 15:04:47 +01:00
3ad556bb3b Fix for requirements bug 2021-08-04 14:52:27 +01:00
833cfb1278 Trying to fix bizarre Requirements bug 2021-08-04 14:43:58 +01:00
5944f6fa85 Updated requirements 2021-08-04 14:33:01 +01:00
cf912db336 Hotfix colour bug 2021-08-04 14:19:11 +01:00
9656249655 Hotfix to enable /tcard 2021-08-04 14:14:38 +01:00
e441ba63a0 Hotfix for enabling the /tcard command 2021-08-04 14:12:14 +01:00
17 changed files with 133 additions and 57 deletions

View File

@ -71,6 +71,12 @@ 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 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`. | | `/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 ### Pitch Command
The `/pitch` command is used to run pitches for games on the server. The `/pitch` command is used to run pitches for games on the server.

View File

@ -17,10 +17,26 @@ 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. 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. 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.
You will also need this database to set up a username and password for the MongoDB database. The following is a step-by-step guide on installation:
The specific username and password don't matter as the bot refers back to the environment variable when authenticating.
### 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.
The following is the template for the `.env` file, with the variable names as are referenced in the bot's code:
`.env` file: `.env` file:
```DotENV ```DotENV
@ -37,8 +53,7 @@ BOT_VERSION=(verson string)
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. 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.**: 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. **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:
To change this, navigate to the last line of the file `bot.py` and change the line:
```py ```py
client.run(os.getenv('TEST_TOKEN')) client.run(os.getenv('TEST_TOKEN'))
@ -56,7 +71,9 @@ in order for to authenticate as the correct bot.
``` ```
|-- app |-- app
| |-- .env | |-- .env.example
| |-- assets
| | `-- tcard.wav
| |-- bot.py | |-- bot.py
| |-- cogs | |-- cogs
| | |-- botcommands | | |-- botcommands
@ -88,7 +105,8 @@ in order for to authenticate as the correct bot.
| | |-- game_management.py | | |-- game_management.py
| | |-- manipulate_timeslots.py | | |-- manipulate_timeslots.py
| | |-- pitch.py | | |-- pitch.py
| | `-- player_commands.py | | |-- player_commands.py
| | `-- tcard.py
| |-- data | |-- data
| | |-- .gitkeep | | |-- .gitkeep
| | |-- categories.yml | | |-- categories.yml
@ -114,7 +132,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. 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 was considering merging them into one file, but given how different the two concerns were I ended up splitting the files.
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. 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.
### `config.yml` Structure ### `config.yml` Structure

9
app/.env.example Normal file
View File

@ -0,0 +1,9 @@
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,4 +1,6 @@
FROM python:3.9.6-buster FROM python:3.9.6-buster
RUN apt-get update -y && apt-get upgrade -y && apt-get install libopus0 -y
COPY . /usr/src/app
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN pip install --upgrade pip RUN pip install --upgrade pip
RUN pip install -r requirements.txt RUN pip install -r requirements.txt

Binary file not shown.

View File

@ -75,7 +75,8 @@ def getPrefix(client, message):
# Define Clients # Define Clients
client = commands.Bot( client = commands.Bot(
intents=discord.Intents.all(), intents=discord.Intents.all(),
command_prefix=getPrefix 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.'
) )
slash = SlashCommand( slash = SlashCommand(
client, client,
@ -278,6 +279,7 @@ if yaml_load(configFile):
if any([x for x in yaml_load(lookupFile).values()]): if any([x for x in yaml_load(lookupFile).values()]):
loadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') loadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py')
loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py') loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
loadCog(f'./{cogsDir}/slashcommands/secondary/tcard.py')
loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py') loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
if yaml_load(pitchesFile): if yaml_load(pitchesFile):
loadCog(f'./{cogsDir}/events/secondary/pitch_listener.py') loadCog(f'./{cogsDir}/events/secondary/pitch_listener.py')

View File

@ -24,9 +24,6 @@ class Debug(commands.Cog, name='Debug Commands'):
if role.id == conf[str(ctx.guild.id)]['roles']['maintainer']: return True if role.id == conf[str(ctx.guild.id)]['roles']['maintainer']: return True
return ctx.author.id == ctx.guild.owner_id 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( @commands.command(
name='testconfig', name='testconfig',
description='Tests the completeness of the configuration values of the current guild by comparing it to a configuration blueprint.', description='Tests the completeness of the configuration values of the current guild by comparing it to a configuration blueprint.',

View File

@ -16,8 +16,12 @@ class Migrate(commands.Cog, name='Migrate Command'):
self.client = client self.client = client
#### Permission Check: Only available to the bot's maintainer. #### Permission Check: Only available to the bot's maintainer.
async def cog_check(self, ctx): async def cog_check(self, ctx:commands.Context):
return ctx.author.id == int(os.getenv('BOT_MAINTAINER_ID')) 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( @commands.command(
name='migrate', name='migrate',

View File

@ -66,13 +66,14 @@ class PitchListener(commands.Cog, name='Pitch Listener'):
if set([x.id for x in ctx.author.roles]) & set(pitches[guildStr][timeslot]['roles'].values()): 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())): 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) role = ctx.guild.get_role(r)
rStr = str(role.id)
if role.id != pitches[guildStr][timeslot]['roles'][index]: if role.id != pitches[guildStr][timeslot]['roles'][index]:
await ctx.author.remove_roles(role,reason=f'/pitch interaction by {ctx.author.display_name}') await ctx.author.remove_roles(role,reason=f'/pitch interaction by {ctx.author.display_name}')
i = pitches[guildStr][timeslot]['indices'][role.id] i = pitches[guildStr][timeslot]['indices'][role.id]
element = pitches[guildStr][timeslot]['entries'][i] element = pitches[guildStr][timeslot]['entries'][i]
gm = await self.client.fetch_user(element['gm']) gm = await self.client.fetch_user(element['gm'])
if ctx.author.id != lookup[guildStr][str(role.id)]['gm']: if ctx.author.id != lookup[guildStr][rStr]['gm']:
data[guildStr][timeslot][str(role.id)]['current_players'] -= 1 data[guildStr][timeslot][rStr]['current_players'] -= 1
element['current_players'] -= 1 element['current_players'] -= 1
o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n' 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['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n'])
@ -84,9 +85,9 @@ class PitchListener(commands.Cog, name='Pitch Listener'):
o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}']) 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]) m = await ctx.channel.fetch_message(pitches[guildStr][timeslot]['messages'][i])
await m.edit(content=o) await m.edit(content=o)
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['text_channel'],ctx.guild.text_channels) tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.text_channels)
if tc is None: if tc is None:
c = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['category'],ctx.guild.categories) c = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['category'],ctx.guild.categories)
if c is not None: if c is not None:
tPos = len(ctx.guild.channels) tPos = len(ctx.guild.channels)
for t in c.text_channels: for t in c.text_channels:
@ -96,13 +97,15 @@ class PitchListener(commands.Cog, name='Pitch Listener'):
if tc is not None: if tc is not None:
await tc.send(f'```{ctx.author.display_name} has left the game.```') await tc.send(f'```{ctx.author.display_name} has left the game.```')
role = ctx.guild.get_role(pitches[guildStr][timeslot]['roles'][index]) role = ctx.guild.get_role(pitches[guildStr][timeslot]['roles'][index])
rStr = str(role.id)
if role in ctx.author.roles: if role in ctx.author.roles:
await ctx.send(f'```Error: You are already in the game `{lookup[guildStr][str(role.id)]["game_title"]}`.```', hidden=True) await ctx.send(f'```Error: You are already in the game `{lookup[guildStr][rStr]["game_title"]}`.```', hidden=True)
else: else:
await ctx.author.add_roles(role,reason=f'/pitch interaction by {ctx.author.display_name}') await ctx.author.add_roles(role,reason=f'/pitch interaction by {ctx.author.display_name}')
element = pitches[guildStr][timeslot]['entries'][index] element = pitches[guildStr][timeslot]['entries'][index]
data[guildStr][timeslot][str(role.id)]['current_players'] += 1 if ctx.author.id != lookup[guildStr][rStr]['gm']:
element['current_players'] += 1 data[guildStr][timeslot][rStr]['current_players'] += 1
element['current_players'] += 1
gm = await self.client.fetch_user(element['gm']) gm = await self.client.fetch_user(element['gm'])
o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n' 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['system'] is not None: o = ''.join([o,f'System: {element["system"]}\n'])
@ -114,10 +117,10 @@ class PitchListener(commands.Cog, name='Pitch Listener'):
o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}']) 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]) m = await ctx.channel.fetch_message(pitches[guildStr][timeslot]['messages'][index])
await m.edit(content=o) await m.edit(content=o)
await ctx.send(f'You have joined the game `{lookup[guildStr][str(role.id)]["game_title"]}`.',hidden=True) 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][str(role.id)]['text_channel'],ctx.guild.text_channels) tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.text_channels)
if tc is None: if tc is None:
c = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['category'],ctx.guild.categories) c = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['category'],ctx.guild.categories)
if c is not None: if c is not None:
tPos = len(ctx.guild.channels) tPos = len(ctx.guild.channels)
for t in c.text_channels: for t in c.text_channels:
@ -126,15 +129,27 @@ class PitchListener(commands.Cog, name='Pitch Listener'):
tPos = t.position tPos = t.position
if tc is not None: if tc is not None:
await tc.send(f'```{ctx.author.display_name} has joined the game.```') 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_'): elif ctx.custom_id.startswith('leave_'):
role = ctx.guild.get_role(pitches[guildStr][timeslot]['roles'][index]) role = ctx.guild.get_role(pitches[guildStr][timeslot]['roles'][index])
rStr = str(role.id)
if role not in ctx.author.roles: if role not in ctx.author.roles:
await ctx.send(f'```Error: You are not in the game `{lookup[guildStr][str(role.id)]["game_title"]}`.```', hidden=True) await ctx.send(f'```Error: You are not in the game `{lookup[guildStr][rStr]["game_title"]}`.```', hidden=True)
else: else:
await ctx.author.remove_roles(role,reason=f'/pitch interaction by {ctx.author.display_name}') await ctx.author.remove_roles(role,reason=f'/pitch interaction by {ctx.author.display_name}')
element = pitches[guildStr][timeslot]['entries'][index] element = pitches[guildStr][timeslot]['entries'][index]
if ctx.author.id != lookup[guildStr][str(role.id)]['gm']: if ctx.author.id != lookup[guildStr][rStr]['gm']:
data[guildStr][timeslot][str(role.id)]['current_players'] -= 1 data[guildStr][timeslot][rStr]['current_players'] -= 1
element['current_players'] -= 1 element['current_players'] -= 1
gm = await self.client.fetch_user(element['gm']) gm = await self.client.fetch_user(element['gm'])
o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n' o = f'_ _\n***{element["game_title"]}*** (GM: {gm.mention})\n```\n'
@ -147,10 +162,10 @@ class PitchListener(commands.Cog, name='Pitch Listener'):
o = ''.join([o,f'~~Spaces Remaining: {str(0)}~~'])if spaces_remaining <= 0 else ''.join([o,f'Spaces Remaining: {str(spaces_remaining)}']) 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]) me = await ctx.channel.fetch_message(pitches[guildStr][timeslot]['messages'][index])
await me.edit(content=o) await me.edit(content=o)
await ctx.send(f'You have left the game `{lookup[guildStr][str(role.id)]["game_title"]}`.',hidden=True) 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][str(role.id)]['text_channel'],ctx.guild.text_channels) tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.text_channels)
if tc is None: if tc is None:
c = discord.utils.find(lambda x: x.id == lookup[guildStr][str(role.id)]['category'],ctx.guild.categories) c = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['category'],ctx.guild.categories)
if c is not None: if c is not None:
tPos = len(ctx.guild.channels) tPos = len(ctx.guild.channels)
for t in c.text_channels: for t in c.text_channels:
@ -159,6 +174,17 @@ class PitchListener(commands.Cog, name='Pitch Listener'):
tPos = t.position tPos = t.position
if tc is not None: if tc is not None:
await tc.send(f'```{ctx.author.display_name} has left the game.```') 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(data, dataFile)
yaml_dump(pitches, pitchesFile) yaml_dump(pitches, pitchesFile)

View File

@ -27,14 +27,14 @@ class MemberVerification(commands.Cog, name='Member Verification Cog'):
if message.author.bot: return if message.author.bot: return
if message.channel.id != conf[guildStr]['channels']['signup']: return if message.channel.id != conf[guildStr]['channels']['signup']: return
if not (message.attachments): if not (message.attachments):
await message.channel.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.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() await message.delete()
return return
membership = [discord.utils.get(message.guild.roles, id=x) for x in conf[guildStr]['membership']] membership = [discord.utils.get(message.guild.roles, id=x) for x in conf[guildStr]['membership']]
membership_options = [create_select_option(label=x.name, value=str(x.id), description='Membership type.') for x in membership] membership_options = [create_select_option(label=x.name, value=str(x.id), description='Membership type.') for x in membership]
admin_buttons = [] 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}')) if conf[guildStr]['roles'].get('student', None) is not None: admin_buttons.append(create_button(style=ButtonStyle.blurple, label='Student', emoji='📚', custom_id=f'student_{message.id}'))
admin_buttons.append(create_button(style=ButtonStyle.grey, label='Alert', emoji='⚠️', custom_id=f'alert_{message.id}')) admin_buttons.append(create_button(style=ButtonStyle.grey, label='Review', emoji='⚠️', custom_id=f'review_{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.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}')) 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 by `{message.author.display_name}`.```'
@ -89,36 +89,36 @@ class MemberVerification(commands.Cog, name='Member Verification Cog'):
description = f'[Jup to Message]({submission.jump_url})', description = f'[Jup to Message]({submission.jump_url})',
colour = discord.Colour.red(), colour = discord.Colour.red(),
) )
await submission.channel.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.```') 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: 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 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() await ctx.origin_message.delete()
elif ctx.custom_id.startswith('alert_'): elif ctx.custom_id.startswith('review_'):
await ctx.send(f'```Membership verification alert raised.```', hidden=True) await ctx.send(f'```Membership review requested.```', hidden=True)
embed = discord.Embed( embed = discord.Embed(
title = submission.author.name, title = submission.author.name,
description = f'[Jup to Message]({submission.jump_url})', description = f'[Jup to Message]({submission.jump_url})',
colour = discord.Colour.orange() colour = discord.Colour.orange()
) )
await submission.channel.send(f'```Your membership for guild `{submission.guild.name}` needs to be reviewed by a Committee member.```') 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: 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) 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_'): elif ctx.custom_id.startswith('student_'):
await ctx.send(f'````Student` role granted.```', hidden=True) await ctx.send(f'````Student` role granted.```', hidden=True)
student_role = submission.guild.get_role(conf[guildStr]['roles']['student']) 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.add_roles(student_role, reason=f'Membership Verification: Student role assigned by `{ctx.author.display_name}`.')
await submission.channel.send(f'```You have additionally been assigned the role `Student` in the guild `{submission.guild.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_'): elif ctx.custom_id.startswith('membership_'):
[selected_membership] = ctx.selected_options [selected_membership] = ctx.selected_options
selected_role = ctx.guild.get_role(int(selected_membership)) selected_role = ctx.guild.get_role(int(selected_membership))
if selected_role not in submission.author.roles: 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 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.add_roles(selected_role, reason=f'Membership Verification: Membership verified by `{ctx.author.display_name}`.')
await submission.channel.send(f'```Your membership for guild `{submission.guild.name}` has been verified and you have been assigned the role `{selected_role.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: else:
await ctx.send(f'```Membership `{selected_role.name}` removed from member `{submission.author.display_name}`.```', hidden=True) 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.remove_roles(selected_role, reason=f'Membership Verification: Membership removed by `{ctx.author.display_name}`.')
await submission.channel.send(f'```Your role `{selected_role.name}` has been removed in the guild `{submission.guild.name}`.```') await submission.author.send(f'```Your role `{selected_role.name}` has been removed in the guild `{submission.guild.name}`.```')
else: else:
pass pass

View File

@ -123,6 +123,9 @@ class Configuration(commands.Cog, name='Configuration Commands'):
if self.client.get_cog('Player Commands') is None: if self.client.get_cog('Player Commands') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py') loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
flag = True 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: if self.client.get_cog('Pitch Command') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py') loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
flag = True flag = True
@ -301,6 +304,9 @@ class Configuration(commands.Cog, name='Configuration Commands'):
if self.client.get_cog('Player Commands') is None: if self.client.get_cog('Player Commands') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py') loadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
Flag = True 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: if self.client.get_cog('Pitch Command') is None:
loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py') loadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
Flag = True Flag = True

View File

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

View File

@ -97,6 +97,7 @@ class GameManagement(commands.Cog, name='Game Management'):
if not any([x for x in yaml_load(lookupFile).values()]): if not any([x for x in yaml_load(lookupFile).values()]):
unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') 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('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('Pitch Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
await self.client.slash.sync_all_commands() await self.client.slash.sync_all_commands()
@ -553,6 +554,7 @@ class GameManagement(commands.Cog, name='Game Management'):
if not any([x for x in yaml_load(lookupFile).values()]): if not any([x for x in yaml_load(lookupFile).values()]):
unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') 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('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('Pitch Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
await self.client.slash.sync_all_commands() await self.client.slash.sync_all_commands()

View File

@ -91,14 +91,11 @@ class ManipulateTimeslots(commands.Cog, name='Manipulate Timeslots'):
yaml_dump(conf, configFile) yaml_dump(conf, configFile)
if not any([x['timeslots'] for x in yaml_load(configFile).values()]): if not any([x['timeslots'] for x in yaml_load(configFile).values()]):
unloadCog(f'./{cogsDir}/slashcommands/secondary/manipulate_timeslots.py') unloadCog(f'./{cogsDir}/slashcommands/secondary/manipulate_timeslots.py')
if self.client.get_cog('Game Management') is not None: if self.client.get_cog('Game Management') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py')
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('Game Create') is not None: if self.client.get_cog('Player Commands') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/player_commands.py')
unloadCog(f'./{cogsDir}/slashcommands/secondary/game_create.py') if self.client.get_cog('T-Card Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/tcard.py')
if self.client.get_cog('Player Commands') is not None: if self.client.get_cog('Pitch Command') is not None: unloadCog(f'./{cogsDir}/slashcommands/secondary/pitch.py')
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() await self.client.slash.sync_all_commands()
else: else:
await ctx.send('```Error: You cannot delete a timeslot that has existing game entries. Please delete all games first.```',hidden=True) await ctx.send('```Error: You cannot delete a timeslot that has existing game entries. Please delete all games first.```',hidden=True)

View File

@ -148,7 +148,7 @@ class PlayerCommands(commands.Cog, name='Player Commands'):
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.channels) tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.channels)
if tc is not None: if tc is not None:
p = await tc.pins() p = await tc.pins()
if p: if p is not None:
header = discord.utils.find(lambda x: x.id == hm, p) header = discord.utils.find(lambda x: x.id == hm, p)
if header is not None: if header is not None:
text = header.content.split('\n') text = header.content.split('\n')
@ -211,7 +211,7 @@ class PlayerCommands(commands.Cog, name='Player Commands'):
tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.channels) tc = discord.utils.find(lambda x: x.id == lookup[guildStr][rStr]['text_channel'],ctx.guild.channels)
if tc is not None: if tc is not None:
p = await tc.pins() p = await tc.pins()
if p: if p is not None:
header = discord.utils.find(lambda x: x.id == hm, p) header = discord.utils.find(lambda x: x.id == hm, p)
if header is not None: if header is not None:
text = header.content.split('\n') text = header.content.split('\n')

View File

@ -37,11 +37,10 @@ class TCardCommand(commands.Cog, name='T-Card Command'):
gms = yaml_load(gmFile) gms = yaml_load(gmFile)
categories = yaml_load(categoriesFile) categories = yaml_load(categoriesFile)
guildStr = str(ctx.guild.id) guildStr = str(ctx.guild.id)
# rStr = str(game.id)
embed = discord.Embed( embed = discord.Embed(
title='T-Card', title='T-Card',
description='A T-Card Has Been Played', description='A T-Card Has Been Played',
colour=discord.Color.dark_red, colour=discord.Color.dark_red(),
) )
embed.set_footer(text=datetime.now().strftime('%a %-d %b %y, %-I:%M %p')) 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') embed.set_image(url='http://geas.org.uk/wp-content/uploads/2020/08/tcard-1-e1597167776966.png')
@ -68,12 +67,12 @@ class TCardCommand(commands.Cog, name='T-Card Command'):
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) 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.""" """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: for vc in ctx.channel.category.voice_channels:
v = await vc.connect() v = await vc.connect()
tcardaudio = discord.PCMAudio(open("./assets/tcard.wav", "rb")) tcardaudio = discord.PCMAudio(open("./assets/tcard.wav", "rb"))
v.play(tcardaudio) v.play(tcardaudio)
while v.is_playing(): while v.is_playing(): time.sleep(.1)
time.sleep(1)
await v.disconnect() await v.disconnect()
else: else:
"""Send a T-Card to the immediate channel if this is a generic channel.""" """Send a T-Card to the immediate channel if this is a generic channel."""

View File

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