Updated bot.

Bugfix: player count on GM leaving
Added /tcard command with audio
Updated debug commands
This commit is contained in:
2021-08-04 13:15:18 +01:00
parent 40daa58326
commit 9014bdaac4
10 changed files with 129 additions and 77 deletions

View File

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

@ -0,0 +1,191 @@
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 pprint import pprint
from bot import clearConfig, configFile, checkConfig, loadCog, loadCogs, setConfig, unloadCog, unloadCogs, yaml_dump, yaml_load, reloadCog, reloadCogs, pitchesFile, cogsDir, parseConfigCheck
##### Debug Cog
class Debug(commands.Cog, name='Debug Commands'):
def __init__(self, client):
self.client = client
#### Permission Check: Only available to the bot's maintainer.
async def cog_check(self, ctx: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='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.```")
@commands.command(
name='reloadcogs',
description='Reloads cogs within the specified category, or provide `all` for all cogs. Default: `all`.',
brief='Reload multiple cogs by category.'
)
async def _reload(self, ctx, cog_category: str='--all'):
reloadCogs(cog_category)
await ctx.reply(f'````{cog_category}` cogs have been reloaded.```')
@commands.command(
name='unloadcogs',
description='Unload cogs within the specified category, or provide `all` for all cogs. Default: `all`.',
brief='Unload multiple cogs by category.'
)
async def _unloadcogs(self, ctx, cog_category: str='--all'):
unloadCogs(cog_category)
loadCogs(cog_category)
await ctx.reply(f'````{cog_category}` cogs have been unloaded.```')
@commands.command(
name='loadcogs',
description='Load cogs within the specified category, or provide `all` for all cogs. Default: `all`.',
brief='Load multiple cogs by category.'
)
async def _loadcogs(self, ctx, cog_category: str='--all'):
unloadCogs(cog_category)
loadCogs(cog_category)
await ctx.reply(f'````{cog_category}` cogs have been loaded.```')
@commands.command(
name='retrievecommands',
aliases=['slashcommands','retrieveslashcommands'],
description='Debugging command that retrieves all slash commands currently registered for this guild and this bot to the Python console.',
brief='Retrieves registered slash commands to console.'
)
async def _retrievecommands(self, ctx:commands.Context):
c = await utils.manage_commands.get_all_commands(
bot_id=self.client.user.id,
bot_token=os.getenv('BOT_TOKEN'),
guild_id=ctx.guild.id
)
pprint(c)
await ctx.reply(f'```All registered `/commands` have been fetched and sent to the Python console.```')
@commands.command(
name='deletecommand',
aliases=['removecommand','delcommand','removeslashcommand', 'clearcommand', 'clearslashcommand'],
description='Debugging command that deletes a specified slash command. Key parameters `--all` for all commands in guild and `--global` for all commands globally',
brief='Deletes slash command. Default: all local commands.'
)
async def _deleteCommand(self, ctx:commands.Context, command: str='--all'):
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'),
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'),
guild_ids=None
)
await utils.manage_commands.remove_all_commands(
bot_id=self.client.user.id,
bot_token=os.getenv('BOT_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'),
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'),
guild_id=ctx.guild.id,
cmd_id=target
)
await ctx.reply(f'```Slash command {command} has been deleted for the guild {ctx.guild.name}.```')
@commands.command(
name='addcommand',
aliases=['installcommand','addslashcommand'],
description='Adds a slash command to the guild. Use keyword `--global` to add command globally.',
brief='Adds slash command'
)
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'),
guild_id= None if key == '--global' or key == '-g' else ctx.guild.id,
cmd_name=command,
description='No Description'
)
await ctx.reply(f'```The command /{command} has been added for the guild {ctx.guild.name}.```')
@commands.command(
name='clearconfig',
aliases=['configclear'],
description='Clears any redundant entries in the config file of guilds the bot is not in. Does not require any argument.',
brief='Clears redundant entries from config file.'
)
async def _clearconfig(self, ctx:commands.Context):
conf = yaml_load(configFile)
for key in list(conf):
clearConfig(key)
await ctx.reply(f'```Config entries for unknown guilds have been cleared.```')
@commands.command(
name='setconfig',
aliases=['configsetup'],
description='Creates a config entry for the current guild, and if there are existing entries sets default values. Ignores any values that have already been set. Does not require any argument as it infers the guild from the context in which it was called.',
brief='Sets config entry for the current guild.'
)
async def _setconfig(self, ctx:commands.Context):
setConfig(ctx.guild)
await ctx.reply(f'```Config entry has been added for guild `{ctx.guild.name}`.```')
@commands.command(
name='synccommands',
aliases=['syncallcommands','syncslashcommands','resynccommands','sync','resync','syncall','resyncall'],
description='Syncs all slash commands between the bot and the Server.',
brief='Resyncs slash commands.'
)
async def _synccommands(self, ctx:commands.Context):
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

@ -71,8 +71,9 @@ class PitchListener(commands.Cog, name='Pitch Listener'):
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
if ctx.author.id != lookup[guildStr][str(role.id)]['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"])} '])
@ -131,8 +132,10 @@ class PitchListener(commands.Cog, name='Pitch Listener'):
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]
if ctx.author.id != lookup[guildStr][str(role.id)]['gm']:
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'])

View File

@ -59,6 +59,10 @@ class Configuration(commands.Cog, name='Configuration Commands'):
create_choice(
name='`Student` role',
value='student'
),
create_choice(
name='`Bot Maintainer` role',
value='maintainer'
)
]
),

View File

@ -345,7 +345,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}` 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 = 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 = ''.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'])

View File

@ -200,6 +200,9 @@ 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

View File

@ -0,0 +1,84 @@
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)
# rStr = str(game.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."""
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))