Debugged membership sign-ups and pitch menu.

Ready for more rigorous testing.
This commit is contained in:
Vivek Santayana 2021-07-24 17:58:23 +01:00
parent 173aeb2a3c
commit e30e89e7e3
13 changed files with 356 additions and 245 deletions

View File

@ -31,6 +31,7 @@ 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.) 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.) 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.) 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_VERSION=(verson string)
BOT_MAINTAINER_ID=(Discord user ID of the person maintaining the bot to enable debug features.) BOT_MAINTAINER_ID=(Discord user ID of the person maintaining the bot to enable debug features.)
``` ```
@ -74,7 +75,9 @@ in order for to authenticate as the correct bot.
| | | |-- on_guild_role_update.py | | | |-- on_guild_role_update.py
| | | |-- on_guild_update.py | | | |-- on_guild_update.py
| | | |-- on_message.py | | | |-- on_message.py
| | | `-- on_ready.py | | | |-- on_ready.py
| | | `-- secondary
| | | `-- pitch_listener.py
| | |-- membership | | |-- membership
| | | |-- membership_verification.py | | | |-- membership_verification.py
| | | `-- restriction_listener.py | | | `-- restriction_listener.py
@ -194,8 +197,10 @@ 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. 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. 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 ### Membership sign up performance issues
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). 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 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 also mean that the sign-up prompts should persist over reboots.
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.

View File

@ -27,34 +27,34 @@ 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. # 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' 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): if not os.path.exists(configFile): yaml_dump({},configFile)
yaml_dump({},configFile)
# Locate or create data file. Same as above. # 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' 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): if not os.path.exists(dataFile): yaml_dump({},dataFile)
yaml_dump({},dataFile)
# Locate or create lookup file. Same as above. # 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' 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): if not os.path.exists(lookupFile): yaml_dump({},lookupFile)
yaml_dump({},lookupFile)
# Locate or create GM lookup file. Same as above. # 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' 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): if not os.path.exists(gmFile): yaml_dump({},gmFile)
yaml_dump({},gmFile)
# Locate or create Categories lookup file. Same as above. # 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' 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): if not os.path.exists(categoriesFile): yaml_dump({},categoriesFile)
yaml_dump({},categoriesFile)
l = [dataFile, lookupFile, gmFile, 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]
if len(set(l)) != len(l): raise Exception('Config Error: there is a clash between two file names.') if len(set(l)) != len(l): raise Exception('Config Error: there is a clash between two file names.')
# Locate Cogs Directory # Locate Cogs Directory
@ -267,6 +267,7 @@ def reloadCogs(cogClass:str = '--all'):
loadCogs('controlcommands') loadCogs('controlcommands')
loadCogs('events') loadCogs('events')
loadCogs('membership')
loadCogs('botcommands') loadCogs('botcommands')
loadCogs('slashcommands') loadCogs('slashcommands')
if yaml_load(configFile): if yaml_load(configFile):
@ -279,6 +280,8 @@ if yaml_load(configFile):
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/pitch.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)]): if any([len(yaml_load(configFile)[x]['membership']) > 0 for x in yaml_load(configFile)]):
loadCog(f'./{cogsDir}/slashcommands/secondary/edit_membership.py') loadCog(f'./{cogsDir}/slashcommands/secondary/edit_membership.py')

View File

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

@ -0,0 +1,163 @@
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 _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)
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'])
data[guildStr][timeslot][str(role.id)]['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][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'```{ctx.author.display_name} has left the game.```')
role = ctx.guild.get_role(pitches[guildStr][timeslot]['roles'][index])
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)
else:
await ctx.author.add_roles(role,reason=f'/pitch interaction by {ctx.author.display_name}')
element = pitches[guildStr][timeslot]['entries'][index]
data[guildStr][timeslot][str(role.id)]['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][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'```{ctx.author.display_name} has joined the game.```')
elif ctx.custom_id.startswith('leave_'):
role = ctx.guild.get_role(pitches[guildStr][timeslot]['roles'][index])
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)
else:
await ctx.author.remove_roles(role,reason=f'/pitch interaction by {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)}'])
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][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'```{ctx.author.display_name} has left the game.```')
yaml_dump(data, dataFile)
yaml_dump(pitches, pitchesFile)
def setup(client):
client.add_cog(PitchListener(client))

View File

@ -3,8 +3,9 @@ import yaml # YAML parser for Bot config files
import asyncio # Discord Py Dependency import asyncio # Discord Py Dependency
import discord # Main Lib import discord # Main Lib
from discord.ext import commands # Commands module from discord.ext import commands # Commands module
from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash Command Library 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, create_choice, create_option 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.model import ButtonStyle from discord_slash.model import ButtonStyle
import logging import logging
# logger and handler # logger and handler
@ -16,16 +17,17 @@ class MemberVerification(commands.Cog, name='Member Verification Cog'):
def __init__(self, client): def __init__(self, client):
self.client = client self.client = client
@commands.Cog.listener() @commands.Cog.listener(name='on_message')
async def on_message(self, message): async def _submission_listener(self, message):
conf = yaml_load(configFile) conf = yaml_load(configFile)
categories = yaml_load(categoriesFile) categories = yaml_load(categoriesFile)
guildStr = str(message.guild.id) guildStr = str(message.guild.id)
lookup = yaml_load(lookupFile) lookup = yaml_load(lookupFile)
if conf[guildStr]['channels'].get('signup', None) is not None: return if conf[guildStr]['channels'].get('signup', None) is None: 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.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.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.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']]
@ -35,9 +37,9 @@ class MemberVerification(commands.Cog, name='Member Verification Cog'):
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='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.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.```\n' o = f'```For Administrators: Please verify the membership request submitted by `{message.author.display_name}`.```'
admins = '|'.join([discord.utils.get(message.guild.roles, id=x).mention for x in conf[guildStr]['roles']['admin']]) admins = '|'.join([discord.utils.get(message.guild.roles, id=x).mention for x in conf[guildStr]['roles']['admin']])
o = '\n'.join((o,admins)) o = ''.join((admins,o))
m = await message.reply( m = await message.reply(
content= o, content= o,
components=[ components=[
@ -55,55 +57,70 @@ class MemberVerification(commands.Cog, name='Member Verification Cog'):
) )
] ]
) )
while True: if conf[guildStr]['notifications'].get('signup', False):
interaction_ctx = await wait_for_component(self.client, messages=m) embed = discord.Embed(
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): title = f'Member Verification Request',
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) description = f'User: {message.author.name}\n\n[Jup to Message]({m.jump_url})',
else: colour = discord.Colour.blue(),
submission = await interaction_ctx.channel.fetch_message(int(interaction_ctx.custom_id.split('_',1)[1])) )
if interaction_ctx.custom_id.startswith('done_'): if conf[guildStr]['channels'].get('mod', None) is not None:
await interaction_ctx.send(f'```Membership verification complete.```', hidden=True) await message.guild.get_channel(conf[guildStr]['channels']['mod']).send(f'```New membership verification request.```\n{admins}', embed=embed)
break
elif interaction_ctx.custom_id.startswith('deny_'):
await interaction_ctx.send(f'```Membership verification denied.```', hidden=True) @commands.Cog.listener(name='on_component')
embed = discord.Embed( async def _verification_response(self, ctx:ComponentContext):
title = submission.author.name, conf = yaml_load(configFile)
description = f'[Jup to Message]({submission.jump_url})', categories = yaml_load(categoriesFile)
colour = discord.Colour.red(), guildStr = str(ctx.guild.id)
) admins = '|'.join([discord.utils.get(ctx.guild.roles, id=x).mention for x in conf[guildStr]['roles']['admin']])
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.```') lookup = yaml_load(lookupFile)
if conf[guildStr]['channels'].get('mod', None) is not None: if ctx.channel.id != conf[guildStr]['channels']['signup']: return
await submission.guild.get_channel(conf[guildStr]['channels']['mod']).send(f'```Verifying the membership of {submission.author.display_name} failed.```\n{admins}', embed=embed) 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):
break 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)
elif interaction_ctx.custom_id.startswith('alert_'): else:
await interaction_ctx.send(f'```Membership verification alert raised.```', hidden=True) submission = await ctx.channel.fetch_message(int(ctx.custom_id.split('_',1)[1]))
embed = discord.Embed( if ctx.custom_id.startswith('done_'):
title = submission.author.name, await ctx.send(f'```Membership verification complete.```', hidden=True)
description = f'[Jup to Message]({submission.jump_url})', await ctx.origin_message.delete()
colour = discord.Colour.orange() elif ctx.custom_id.startswith('deny_'):
) await ctx.send(f'```Membership verification denied.```', hidden=True)
await submission.author.send(f'```Your membership for guild `{submission.guild.name}` needs to be reviewed by a Committee member.```') embed = discord.Embed(
if conf[guildStr]['channels'].get('mod', None) is not None: title = submission.author.name,
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) description = f'[Jup to Message]({submission.jump_url})',
elif interaction_ctx.custom_id.startswith('student_'): colour = discord.Colour.red(),
await interaction_ctx.send(f'````Student` role granted.```', hidden=True) )
student_role = submission.guild.get_role(conf[guildStr]['roles']['student']) 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.add_roles(student_role, reason=f'Membership Verification: Student role assigned by `{interaction_ctx.author.display_name}`.') if conf[guildStr]['channels'].get('mod', None) is not None:
await submission.author.send(f'```You have additionally been assigned the role `Student` in the guild `{submission.guild.name}`.```') await submission.guild.get_channel(conf[guildStr]['channels']['mod']).send(f'```Verifying the membership of {submission.author.display_name} failed.```\n{admins}', embed=embed)
elif interaction_ctx.custom_id.startswith('membership_'): await ctx.origin_message.delete()
[selected_membership] = interaction_ctx.selected_options elif ctx.custom_id.startswith('alert_'):
selected_role = interaction_ctx.guild.get_role(int(selected_membership)) await ctx.send(f'```Membership verification alert raised.```', hidden=True)
if selected_role not in submission.author.roles: embed = discord.Embed(
await interaction_ctx.send(f'```Membership `{selected_role.name}` added to member `{submission.author.display_name}`.```', hidden=True) title = submission.author.name,
await submission.author.add_roles(selected_role, reason=f'Membership Verification: Membership verified by `{interaction_ctx.author.display_name}`.') description = f'[Jup to Message]({submission.jump_url})',
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}`.```') colour = discord.Colour.orange()
else: )
await interaction_ctx.send(f'```Membership `{selected_role.name}` removed from member `{submission.author.display_name}`.```', hidden=True) await submission.channel.send(f'```Your membership for guild `{submission.guild.name}` needs to be reviewed by a Committee member.```')
await submission.author.remove_roles(selected_role, reason=f'Membership Verification: Membership removed by `{interaction_ctx.author.display_name}`.') if conf[guildStr]['channels'].get('mod', None) is not None:
await submission.author.send(f'```Your role `{selected_role.name}` has been removed in the guild `{submission.guild.name}`.```') 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.channel.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.channel.send(f'```Your membership for guild `{submission.guild.name}` has been verified and you have been assigned the role `{selected_role.name}`.```')
else: else:
pass await ctx.send(f'```Membership `{selected_role.name}` removed from member `{submission.author.display_name}`.```', hidden=True)
await m.delete() 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}`.```')
else:
pass
def setup(client): def setup(client):
client.add_cog(MemberVerification(client)) client.add_cog(MemberVerification(client))

View File

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

View File

@ -9,7 +9,7 @@ from discord_slash.model import SlashCommandPermissionType, ButtonStyle
from discord_slash.client import SlashCommand 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 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 from bot import configFile, yaml_load, yaml_dump, cogsDir, unloadCog, dataFile, lookupFile, gmFile, categoriesFile, pitchesFile, loadCog
#### Pitch Command #### Pitch Command
@ -42,8 +42,12 @@ class Pitch(commands.Cog, name='Pitch Command'):
lookup = yaml_load(lookupFile) lookup = yaml_load(lookupFile)
gms = yaml_load(gmFile) gms = yaml_load(gmFile)
categories = yaml_load(categoriesFile) categories = yaml_load(categoriesFile)
pitches = {}
guildStr = str(ctx.guild.id) 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'] = {} if 'timeslots' not in conf[guildStr]: conf[guildStr]['timeslots'] = {}
tsDict = {k: conf[guildStr]['timeslots'][k] for k in data[guildStr] if data[guildStr][k]} 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] optionsList = [create_select_option(label=tsDict[x], value=x, description=x) for x in tsDict]
@ -83,7 +87,6 @@ class Pitch(commands.Cog, name='Pitch Command'):
ctx.guild.default_role: p ctx.guild.default_role: p
} }
) )
if guildStr not in pitches: pitches[guildStr] = {}
if timeslot not in pitches[guildStr]: pitches[guildStr][timeslot] = {} if timeslot not in pitches[guildStr]: pitches[guildStr][timeslot] = {}
pitches[guildStr][timeslot]['indices'] = {} pitches[guildStr][timeslot]['indices'] = {}
pitches[guildStr][timeslot]['entries'] = [x for x in data[guildStr][timeslot].values()] pitches[guildStr][timeslot]['entries'] = [x for x in data[guildStr][timeslot].values()]
@ -91,6 +94,7 @@ class Pitch(commands.Cog, name='Pitch Command'):
header_message = await ctx.channel.send( 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.```' 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]['messages'] = []
pitches[guildStr][timeslot]['roles'] = {} pitches[guildStr][timeslot]['roles'] = {}
for index, element in enumerate(pitches[guildStr][timeslot]['entries']): for index, element in enumerate(pitches[guildStr][timeslot]['entries']):
@ -122,9 +126,9 @@ class Pitch(commands.Cog, name='Pitch Command'):
) )
] ]
) )
pitches[guildStr][timeslot]['messages'].append(m) pitches[guildStr][timeslot]['messages'].append(m.id)
r = discord.utils.find(lambda x: x.id == element['role'],ctx.guild.roles) r = discord.utils.find(lambda x: x.id == element['role'],ctx.guild.roles)
pitches[guildStr][timeslot]['roles'][index] = r pitches[guildStr][timeslot]['roles'][index] = r.id
pitches[guildStr][timeslot]['indices'][r.id] = index pitches[guildStr][timeslot]['indices'][r.id] = index
newcomer = returning_player = None 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 'newcomer' in conf[guildStr]['roles']: newcomer = discord.utils.find(lambda x: x.id == conf[guildStr]['roles']['newcomer'], ctx.guild.roles)
@ -172,123 +176,11 @@ class Pitch(commands.Cog, name='Pitch Command'):
) )
] ]
) )
while True: pitches[guildStr][timeslot]['control'] = control.id
button_ctx = await wait_for_component( yaml_dump(pitches,pitchesFile)
self.client, if self.client.get_cog('Pitch Listener') is None:
messages=pitches[guildStr][timeslot]['messages'] + [control] loadCog(f'./{cogsDir}/events/secondary/pitch_listener.py')
) #### Activate global pitch listener
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'].values()):
print('Check 0')
for role in list(set(button_ctx.author.roles) & set(pitches[guildStr][timeslot]['roles'].values())):
if role != pitches[guildStr][timeslot]['roles'][index]:
print('check 1')
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
i = pitches[guildStr][timeslot]['indices'][role.id]
element = pitches[guildStr][timeslot]['entries'][i]
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'][i].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.channel.send('```Pitch menu cleared. Pitches have now concluded.```')
def setup(client): def setup(client):
client.add_cog(Pitch(client)) client.add_cog(Pitch(client))

View File

@ -1 +1,4 @@
'864651943820525609': {} '864651943820525609':
'868506573700468787': 868506572421234718
'868506638720577586': 868506637252583494
'868506722187227166': 868506720375300137

View File

@ -2,7 +2,7 @@
channels: channels:
help: 866645822472454206 help: 866645822472454206
mod: 865348933022515220 mod: 865348933022515220
signup: 866110421592965171 signup: 868523157680693278
configured: true configured: true
membership: membership:
- 866795009121714207 - 866795009121714207
@ -12,6 +12,7 @@
signup: true signup: true
owner: 493694762210033664 owner: 493694762210033664
prefix: '-' prefix: '-'
restrict: false
roles: roles:
admin: admin:
- 866642278529368095 - 866642278529368095

View File

@ -1 +1,38 @@
'864651943820525609': {} '864651943820525609':
avatar:
'868506572421234718':
category: 868506573700468787
current_players: 1
game_title: Kyoshi
gm: 864649599671205914
header_message: 868506578934976543
max_players: 5
min_players: null
platform: null
role: 868506572421234718
system: null
text_channel: 868506576695230534
'868506637252583494':
category: 868506638720577586
current_players: 0
game_title: Roku
gm: 864649599671205914
header_message: 868506642545795142
max_players: 5
min_players: null
platform: null
role: 868506637252583494
system: null
text_channel: 868506640347971655
'868506720375300137':
category: 868506722187227166
current_players: 0
game_title: Aang
gm: 864649599671205914
header_message: 868506726029197312
max_players: 5
min_players: null
platform: null
role: 868506720375300137
system: null
text_channel: 868506724045303878

View File

@ -1 +1,5 @@
'864651943820525609': {} '864651943820525609':
'864649599671205914':
- 868506572421234718
- 868506637252583494
- 868506720375300137

View File

@ -1 +1,19 @@
'864651943820525609': {} '864651943820525609':
'868506572421234718':
category: 868506573700468787
game_title: Kyoshi
gm: 864649599671205914
text_channel: 868506576695230534
time: avatar
'868506637252583494':
category: 868506638720577586
game_title: Roku
gm: 864649599671205914
text_channel: 868506640347971655
time: avatar
'868506720375300137':
category: 868506722187227166
game_title: Aang
gm: 864649599671205914
text_channel: 868506724045303878
time: avatar

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

@ -0,0 +1 @@
{}