Changed file structure.
Moved code to main bot and cog files.
This commit is contained in:
parent
b0b417a8d2
commit
1fa5029212
5
.gitignore
vendored
5
.gitignore
vendored
@ -143,8 +143,9 @@ cython_debug/
|
||||
# Local Dev Env Configs
|
||||
Scripts/
|
||||
pyvenv.cfg
|
||||
app/dev_cogs/template.py.tmp
|
||||
app/dev_cogs/events.py.tmp
|
||||
app/cogs/template.py.tmp
|
||||
app/cogs/events.py.tmp
|
||||
app/old_code/
|
||||
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
|
8
TODO.md
8
TODO.md
@ -1,17 +1,18 @@
|
||||
# To Do
|
||||
|
||||
## Bot Architecture
|
||||
[] Simplify directory tree
|
||||
[X] Simplify directory tree
|
||||
[X] Split event listeners into independent cogs.
|
||||
|
||||
## Bot Functionality
|
||||
[] Delete Commands Function
|
||||
[] Register Commands Function
|
||||
[] 'Delete Commands' Function
|
||||
[] 'Register Commands' Function
|
||||
[] Infer Permissions from Config
|
||||
[X] Dynamic Command Prefixes
|
||||
[] Infer Games from Server Structure
|
||||
[] Re-enable logging
|
||||
[X] Delete Dev/Test Functions
|
||||
[] Error handlers
|
||||
[] Debug Features
|
||||
|
||||
## Event Listeners
|
||||
@ -22,6 +23,7 @@
|
||||
[X] Mod Channel Deleted
|
||||
|
||||
## Commands
|
||||
[] Configure Bot function and sub commands
|
||||
[] Migrate existing bot commands
|
||||
|
||||
## Misc
|
||||
|
556
app/bot.py
556
app/bot.py
@ -1,460 +1,136 @@
|
||||
# Import Dependencies
|
||||
import os
|
||||
import configparser
|
||||
import discord
|
||||
from discord.ext import commands, tasks
|
||||
import os, sys # OS Locations
|
||||
from dotenv import load_dotenv # Import OS variables from Dotenv file.
|
||||
load_dotenv() # Load Dotenv. Delete this for production
|
||||
import yaml # Parser for yaml files for config settings.
|
||||
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
|
||||
|
||||
configFile = './config.ini'
|
||||
dataDir = './data'
|
||||
## Define YAML functions
|
||||
|
||||
# Set Intents
|
||||
intents = discord.Intents.all()
|
||||
intents.typing = True
|
||||
intents.presences = True
|
||||
intents.members = True
|
||||
caches = discord.MemberCacheFlags.all()
|
||||
def yaml_load(filepath:str):
|
||||
### Loads a YAML file
|
||||
with open(filepath, 'r') as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
return data
|
||||
|
||||
# Set Prefix
|
||||
p = '¬'
|
||||
def yaml_dump(data:dict, filepath:str):
|
||||
### Dumps a YAML file
|
||||
with open(filepath, 'w') as file:
|
||||
yaml.dump(data, file)
|
||||
|
||||
# Define Global State Dictionary
|
||||
state = {}
|
||||
# Locate or create config file
|
||||
configFile = './data/config.yml'
|
||||
|
||||
# Clients
|
||||
client = commands.Bot(command_prefix=p, description=f'Geas Server Bot v {os.getenv("BOT_VERSION")}. This is a bot to facilitate setting up game channels on the Geas server. The prefix for the bot is {p}. You can interact with and manipulate game channels that have been created with this bot by @-mentioning the relevant role associated with the game.', intents=intents)
|
||||
if not os.path.exists(configFile):
|
||||
yaml_dump({},configFile)
|
||||
|
||||
# Define Game Times Dictionary
|
||||
gameTimes = {
|
||||
'wed': 'WED',
|
||||
'sunaft': 'SUN AFT',
|
||||
'suneve': 'SUN EVE',
|
||||
'oneshot': 'ONE SHOT'
|
||||
}
|
||||
# Locate or create data file
|
||||
dataFile = './data/data.yml'
|
||||
|
||||
# Reference Time Codes
|
||||
def gameTime(arg):
|
||||
return gameTimes.get(arg, 'OTHER')
|
||||
if not os.path.exists(dataFile):
|
||||
yaml_dump({},dataFile)
|
||||
|
||||
# List Time Codes
|
||||
def timeSlotList():
|
||||
l = []
|
||||
for t in gameTimes:
|
||||
l.append(gameTimes[t])
|
||||
l.append('OTHER')
|
||||
return l
|
||||
# Locate Cogs Directory
|
||||
cogsDir = 'cogs'
|
||||
|
||||
# Lookup Game Time Slot
|
||||
def dbFindTimeslot(guild, role):
|
||||
db = dbClient[str(guild.id)]
|
||||
if role.name.split(': ',maxsplit=1)[0] not in timeSlotList():
|
||||
raise commands.CommandError(f'Invalid lookup value: {role.mention} is not a valid game role.')
|
||||
try:
|
||||
for c in db.list_collection_names():
|
||||
ret = db[c].find_one({'role':role.id})
|
||||
if ret != None and c != 'settings':
|
||||
return c
|
||||
except:
|
||||
return role.name.split(': ',maxsplit=1)[0]
|
||||
# --> Temporary disable logging because of verboseness.
|
||||
# ## Logging configuration imported boilerplate from Discord Py Docs
|
||||
# logger = logging.getLogger('discord')
|
||||
# logger.setLevel(logging.DEBUG)
|
||||
# handler = logging.FileHandler(filename='discord.log', encoding='utf-8', mode='w')
|
||||
# handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
|
||||
# logger.addHandler(handler)
|
||||
|
||||
# Lookup Category from Role
|
||||
def dbLookupRole(guild, role):
|
||||
db = dbClient[str(guild.id)]
|
||||
colName = dbFindTimeslot(guild, role)
|
||||
try:
|
||||
catID = db[colName].find_one({'role':role.id})['category']
|
||||
except:
|
||||
for cat in guild.categories:
|
||||
if cat.name == role.name:
|
||||
catID = cat.id
|
||||
break
|
||||
try:
|
||||
return guild.get_channel(catID)
|
||||
except:
|
||||
raise commands.CommandError('Error: The game\'s corresponding category cannot be matched.')
|
||||
#### Dynamic Prefixes
|
||||
def getPrefix(client, message):
|
||||
conf = yaml_load(configFile)
|
||||
return conf[str(message.guild.id)]['prefix']
|
||||
|
||||
# Get Settings from DB
|
||||
def dbGetSettings():
|
||||
try:
|
||||
for db in dbClient.list_database_names():
|
||||
if db not in ['admin','config','local']:
|
||||
state[db] = {
|
||||
'settings': dbClient[db]['settings'].find_one({'_id':0}).copy()
|
||||
}
|
||||
print('Imported settings from database to local state.',state)
|
||||
except Exception as err:
|
||||
print('Failed to get Settings from Database due to error: ',err)
|
||||
# Define Clients
|
||||
client = commands.Bot(
|
||||
intents=discord.Intents.all(),
|
||||
command_prefix=getPrefix
|
||||
)
|
||||
slash = SlashCommand(
|
||||
client,
|
||||
sync_commands = True,
|
||||
sync_on_cog_reload = True
|
||||
)
|
||||
# sync_on_reload is an important parameter that will become relevant when having to reload cogs on changing bot configs.
|
||||
|
||||
# Get list of game role IDs on server
|
||||
def gameRoleIDList(guild):
|
||||
l = []
|
||||
for r in guild.roles:
|
||||
if r.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
l.append(r.id)
|
||||
return l
|
||||
# Define Config keys
|
||||
configKeys = ['adminroles', 'committeerole', 'botrole', 'modchannel', 'name', 'owner', 'prefix']
|
||||
|
||||
# Get list of game role IDs in DB
|
||||
def gameRoleDBList(guild):
|
||||
dbName = str(guild.id)
|
||||
db = dbClient[dbName]
|
||||
l = []
|
||||
try:
|
||||
for ts in timeSlotList():
|
||||
for e in db[ts].find():
|
||||
l.append(e['role'])
|
||||
return l
|
||||
except:
|
||||
pass
|
||||
def setConfig(guild:discord.Guild):
|
||||
#### Check if the bot is missing any config entries for the guilds it is in, and if it is then add it in.
|
||||
#### N.B.: The way the commands work, the bot will have to list specific guilds in which it will synchronise the commands when it is defining them. So it needs to give a list of all the guilds it is part of when the bot loads, which it draws from the config files.
|
||||
#### Because the bot connects to Discord after it loads, it will not be able to introspect and see what guilds it is part of before the commands are first loaded, and it will only add new guilds to the config files after it has already connected.
|
||||
#### The Bot will first need to set up all of its configurations, and then begin loading all other commands once it is ready.
|
||||
conf = yaml_load(configFile)
|
||||
if str(guild.id) not in conf:
|
||||
conf[str(guild.id)] = {}
|
||||
if 'name' not in conf[str(guild.id)] or conf[str(guild.id)]['name'] != guild.name:
|
||||
conf[str(guild.id)]['name'] = guild.name
|
||||
if 'owner' not in conf[str(guild.id)] or conf[str(guild.id)]['owner'] != guild.owner_id:
|
||||
conf[str(guild.id)]['owner'] = guild.owner_id
|
||||
if 'adminroles' not in conf[str(guild.id)] or (type(conf[str(guild.id)]['adminroles']) is not list or len(conf[str(guild.id)]['adminroles']) == 0 or None in conf[str(guild.id)]['adminroles']):
|
||||
conf[str(guild.id)]['adminroles'] = []
|
||||
for role in guild.roles:
|
||||
if not (role.is_bot_managed() or role.is_integration()) and role.permissions.administrator:
|
||||
conf[str(guild.id)]['adminroles'].append(role.id)
|
||||
if 'prefix' not in conf[str(guild.id)]:
|
||||
conf[str(guild.id)]['prefix'] = '-'
|
||||
if 'modchannel' not in conf[str(guild.id)]:
|
||||
if guild.system_channel is None:
|
||||
p = len(guild.channels)
|
||||
c = None
|
||||
for t in guild.text_channels:
|
||||
if t.position < p:
|
||||
p = t.position
|
||||
conf[str(guild.id)]['modchannel'] = t.id
|
||||
else:
|
||||
conf[str(guild.id)]['modchannel'] = guild.system_channel.id
|
||||
yaml_dump(conf, configFile)
|
||||
|
||||
# Get list of game category IDs in DB
|
||||
def gameCategoryDBList(guild):
|
||||
dbName = str(guild.id)
|
||||
db = dbClient[dbName]
|
||||
l = []
|
||||
try:
|
||||
for ts in timeSlotList():
|
||||
for e in db[ts].find():
|
||||
l.append(e['category'])
|
||||
return l
|
||||
except:
|
||||
pass
|
||||
def clearConfig(guildKey:str):
|
||||
#### Delete Configs for Guilds that the Bot is no longer in
|
||||
conf = yaml_load(configFile)
|
||||
if discord.utils.find(lambda g: str(g.id) == guildKey, client.guilds) is None:
|
||||
del conf[guildKey]
|
||||
yaml_dump(conf, configFile)
|
||||
|
||||
# Get list of GM IDs in DB
|
||||
def gmDBList(guild):
|
||||
dbName = str(guild.id)
|
||||
db = dbClient[dbName]
|
||||
l = []
|
||||
try:
|
||||
for ts in timeSlotList():
|
||||
for e in db[ts].find():
|
||||
l.append(e['gm'])
|
||||
return l
|
||||
except:
|
||||
pass
|
||||
def loadCog(filepath:str):
|
||||
path = os.path.normpath(filepath).split(os.path.sep)
|
||||
if path[-1].endswith('.py'):
|
||||
path[-1] = path[-1][:-3]
|
||||
client.load_extension('.'.join(path))
|
||||
|
||||
# Sync Games on Server
|
||||
def syncGames(guild):
|
||||
dbName = str(guild.id)
|
||||
db = dbClient[dbName]
|
||||
try:
|
||||
for ts in timeSlotList():
|
||||
for e in db[ts].find():
|
||||
if e['role'] not in gameRoleIDList(guild):
|
||||
db[ts].delete_many({'role':e['role']})
|
||||
for r in guild.roles:
|
||||
if r.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
if r.id not in gameRoleDBList(guild):
|
||||
gameName = r.name.split(': ',maxsplit=1)[1]
|
||||
colName = r.name.split(': ',maxsplit=1)[0]
|
||||
for c in guild.categories:
|
||||
if c.name == r.name:
|
||||
break
|
||||
permissions = c.overwrites
|
||||
for p in permissions:
|
||||
if isinstance(p,discord.Member) and permissions[p].manage_channels:
|
||||
break
|
||||
g = {
|
||||
'game': gameName,
|
||||
'gm': p.id,
|
||||
'category': c.id,
|
||||
'capacity': 5,
|
||||
'role': r.id
|
||||
}
|
||||
db[colName].replace_one({'role':g['role']}, g, upsert=True)
|
||||
for c in guild.categories:
|
||||
if c.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
if c.id not in gameCategoryDBList(guild):
|
||||
for r in guild.roles:
|
||||
if r.name == c.name:
|
||||
break
|
||||
if r.name != c.name:
|
||||
break
|
||||
colName = r.name.split(': ',maxsplit=1)[0]
|
||||
db[colName].update_one({'role':r.id},{'$set':{'category':c.id}})
|
||||
for p in c.overwrites:
|
||||
if isinstance(p,discord.Member) and c.overwrites[p].manage_channels:
|
||||
break
|
||||
if p.id not in gmDBList(guild):
|
||||
for r in guild.roles:
|
||||
if r.name == c.name:
|
||||
break
|
||||
if r.name != c.name:
|
||||
break
|
||||
colName = r.name.split(': ',maxsplit=1)[0]
|
||||
db[colName].update_one({'role':r.id},{'$set':{'gm':p.id}})
|
||||
print(f'Synced database for server {guild.name}')
|
||||
except:
|
||||
pass
|
||||
def unloadCog(filepath:str):
|
||||
path = os.path.normpath(filepath).split(os.path.sep)
|
||||
if path[-1].endswith('.py'):
|
||||
path[-1] = path[-1][:-3]
|
||||
client.unload_extension('.'.join(path))
|
||||
|
||||
# Sync for Each Guild
|
||||
@tasks.loop(hours=1.0)
|
||||
async def syncGuilds():
|
||||
try:
|
||||
for guild in client.guilds:
|
||||
syncGames(guild)
|
||||
print('Synced database with server games list.')
|
||||
except:
|
||||
pass
|
||||
def loadCogs(cogClass:str = 'all'):
|
||||
for category in os.listdir(f'./{cogsDir}'):
|
||||
if cogClass == 'all' or cogClass == category:
|
||||
for cogfile in os.listdir(f'./{cogsDir}/{category}'):
|
||||
if cogfile.endswith('.py'):
|
||||
loadCog(f'./{cogsDir}/{category}/{cogfile}')
|
||||
|
||||
@client.event
|
||||
async def on_command_error(ctx,error):
|
||||
if isinstance(error, commands.CommandNotFound):
|
||||
await ctx.send(f'Invalid command. Please use `{p}help` to see a list of available commands.')
|
||||
else:
|
||||
await ctx.channel.send(error)
|
||||
def unloadCogs(cogClass:str = 'all'):
|
||||
for category in os.listdir(f'./{cogsDir}'):
|
||||
if cogClass == 'all' or cogClass == category:
|
||||
for cogfile in os.listdir(f'./{cogsDir}/{category}'):
|
||||
if cogfile.endswith('.py'):
|
||||
unloadCog(f'./{cogsDir}/{category}/{cogfile}')
|
||||
|
||||
# On Ready
|
||||
@client.event
|
||||
async def on_ready():
|
||||
|
||||
await client.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=f'{p} commands'))
|
||||
print(f'Bot has logged in as {client.user.name} responding to prefix {p}')
|
||||
print(f'Geas Server Bot version {os.getenv("BOT_VERSION")} by Vivek Santayana')
|
||||
dbGetSettings()
|
||||
syncGuilds.start()
|
||||
loadCogs('dev')
|
||||
loadCogs('events')
|
||||
loadCogs('botcommands')
|
||||
|
||||
# Check Bot Role is Defined
|
||||
def botrole_is_defined():
|
||||
async def predicate(ctx):
|
||||
try:
|
||||
return state[str(ctx.guild.id)]['settings']['botrole'] != None
|
||||
except:
|
||||
raise commands.CommandError(f'Bot role has not been defined. Please set the bot role using the `{p}definebotrole` command first.')
|
||||
return commands.check(predicate)
|
||||
|
||||
# Check if invoked in valid game channel
|
||||
def in_game_channel(ctx):
|
||||
categoriesList = []
|
||||
db = dbClient[str(ctx.guild.id)]
|
||||
try:
|
||||
try:
|
||||
for c in db.list_collection_names():
|
||||
ret = db[c].find({})
|
||||
for e in ret:
|
||||
if ctx.channel.category == ctx.guild.get_channel(e['category']):
|
||||
return True
|
||||
except:
|
||||
if ctx.channel.category.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
if ctx.command.name == 'deletegame':
|
||||
raise commands.CommandError('Error: you must invoke this command in a text channel corresponding to the game you are attempting to delete.')
|
||||
raise commands.CommandError(f'Error: you must invoke this command in the text channel of your game.')
|
||||
|
||||
# setupGame command: <when>, <@GM> <capacity>, <Name of game>
|
||||
@client.command(name='setupgame', aliases=['setup','gamesetup','creategame','gamecreate','create'], description='Use this command to set up the roles and channels for a new game. The syntax is `setup {wed|sunaft|suneve|oneshot|other} {@GM Name} {Capacity} {Name of game}`')
|
||||
@commands.has_permissions(administrator=True)
|
||||
@botrole_is_defined()
|
||||
async def setupGame(ctx,arg1,arg2, arg3: int, *, arg4):
|
||||
if not (arg2.startswith('<@') and not (arg2.startswith('<@&'))):
|
||||
raise commands.CommandError('Invalid argument. The second parameter must @ the GM.')
|
||||
dbName = str(ctx.guild.id)
|
||||
db = dbClient[dbName]
|
||||
colName = gameTime(arg1.lower())
|
||||
if colName == 'OTHER':
|
||||
await ctx.channel.send('Time code not recognised. Game will be categorised as \'Other\'.')
|
||||
await ctx.channel.trigger_typing()
|
||||
gm = int(arg2.replace('<@', '').replace('>', '').replace('!', ''))
|
||||
gmMember = await ctx.guild.fetch_member(gm)
|
||||
cap = int(arg3)
|
||||
gameTitle = f'{colName}: {arg4}'
|
||||
roleExists = False
|
||||
for r in ctx.guild.roles:
|
||||
if r.name == gameTitle:
|
||||
roleExists = True
|
||||
break
|
||||
if not roleExists:
|
||||
r = await ctx.guild.create_role(name=gameTitle)
|
||||
await r.edit(mentionable=True)
|
||||
await gmMember.add_roles(r)
|
||||
categoryExists = False
|
||||
for c in ctx.guild.categories:
|
||||
if c.name == gameTitle:
|
||||
categoryExists = True
|
||||
break
|
||||
ret = state[dbName]['settings']
|
||||
bots = ctx.guild.get_role(ret['botrole'])
|
||||
permissions = {
|
||||
ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False),
|
||||
r: discord.PermissionOverwrite(read_messages=True),
|
||||
bots: discord.PermissionOverwrite(read_messages=True),
|
||||
gmMember: discord.PermissionOverwrite(read_messages=True, manage_messages=True, manage_channels=True, manage_permissions=True,priority_speaker=True,move_members=True,mute_members=True,deafen_members=True),
|
||||
}
|
||||
if not categoryExists:
|
||||
c = await ctx.guild.create_category(name=gameTitle, overwrites=permissions)
|
||||
await c.create_voice_channel(name=f'voice: {gameTitle}', topic=f'Default voice channel for {gameTitle}')
|
||||
t = await c.create_text_channel(name=f'text: {gameTitle}', topic=f'Default text channel for {gameTitle}')
|
||||
await ctx.channel.send(f'Game {r.mention} has been created with GM {gmMember.mention} and space for {cap} players.')
|
||||
await t.send(f'Hello, {gmMember.mention}! Your game channels for {gameTitle} have now been set up.\nYou can also ping your players or interact with the bot commands by mentioning the {r.mention} role.')
|
||||
else:
|
||||
await c.edit(overwrites=permissions)
|
||||
tPos = len(ctx.guild.channels)
|
||||
tFirst = None
|
||||
vExists = False
|
||||
for t in c.text_channels:
|
||||
if t.position <= tPos:
|
||||
tFirst = t
|
||||
tPos = t.position
|
||||
await t.edit(sync_permissions=True)
|
||||
for v in c.voice_channels:
|
||||
await v.edit(sync_permissions=True)
|
||||
vExists = True
|
||||
await ctx.channel.send(f'The category for game {r.mention} has been reset for GM {gmMember.mention} with space for {cap} players.')
|
||||
if tFirst == None:
|
||||
tFirst = await c.create_text_channel(name=f'text: {gameTitle}', topic=f'Default text channel for {gameTitle}')
|
||||
if not vExists:
|
||||
await c.create_voice_channel(name=f'voice: {gameTitle}', topic=f'Default voice channel for {gameTitle}')
|
||||
await tFirst.send(f'Hello, {gmMember.mention}! Your game channels for {gameTitle} have now been set up.\nYou can also ping your players or interact with the bot commands by mentioning the {r.mention} role.')
|
||||
g = {
|
||||
'game': arg4,
|
||||
'gm': gm,
|
||||
'capacity': cap,
|
||||
'category': c.id,
|
||||
'role': r.id
|
||||
}
|
||||
try:
|
||||
db[colName].replace_one({'role':g['role']}, g , upsert=True)
|
||||
except:
|
||||
pass
|
||||
|
||||
@setupGame.error
|
||||
async def clear_error(ctx, error):
|
||||
if isinstance(error, commands.BadArgument):
|
||||
await ctx.channel.send('The number of players in the game must be an integer.')
|
||||
raise error
|
||||
|
||||
# defineBotrole command
|
||||
@client.command(name='definebotrole', aliases=['setbotrole','botrole','botroleset','botroledefine','br'], description='This is a set-up command to define which role is the botrole that the dice bot use. The syntax is `botrole {@Role}`')
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def defineBotrole(ctx, arg):
|
||||
if not arg.startswith('<@&'):
|
||||
raise commands.CommandError('Invalid argument. The argument must @ a role.')
|
||||
dbName = str(ctx.guild.id)
|
||||
colName = 'settings'
|
||||
r = ctx.guild.get_role(int(arg[3:-1]))
|
||||
if dbName not in state:
|
||||
state[dbName] = {}
|
||||
if 'settings' not in state[dbName]:
|
||||
state[dbName]['settings'] = {}
|
||||
ret = state[dbName]['settings']
|
||||
ret['_id'] = 0
|
||||
if 'botrole' not in ret:
|
||||
await ctx.channel.send(f'Bot role for server {ctx.guild.name} not set. Setting {r.mention} to bot role now.')
|
||||
else:
|
||||
await ctx.channel.send(f'Bot role for server {ctx.guild.name} has already been set to {ctx.guild.get_role(ret["botrole"]).mention}. Updating to {r.mention}.')
|
||||
ret['botrole'] = r.id
|
||||
try:
|
||||
db[colName].update_one({'_id':0},{'$set':{'botrole': r.id}}, upsert=True)
|
||||
except:
|
||||
pass
|
||||
|
||||
# deleteGame command
|
||||
@client.command(name='deletegame', aliases=['delete','del', 'removegame', 'delgame', 'gamedel', 'gamedelete'], description='Use this command to delete the role and associated channels for a game. The syntax is `delete {@Game Role}`. **It must be called in a text channel inside the relevant game.**')
|
||||
@commands.has_permissions(administrator=True)
|
||||
@commands.check(in_game_channel)
|
||||
async def deleteGame(ctx, arg):
|
||||
if not arg.startswith('<@&'):
|
||||
raise commands.CommandError('Invalid argument. The argument must @ a role.')
|
||||
r = ctx.guild.get_role(int(arg[3:-1]))
|
||||
cat = dbLookupRole(ctx.guild, r)
|
||||
dbName = str(ctx.guild.id)
|
||||
db = dbClient[dbName]
|
||||
await ctx.channel.trigger_typing()
|
||||
try:
|
||||
colName = dbFindTimeslot(ctx.guild,r)
|
||||
except:
|
||||
raise commands.CommandError(f'Invalid argument. {r.mention} role is not a valid game role.')
|
||||
for t in ctx.guild.text_channels:
|
||||
if t.category == cat:
|
||||
await t.delete()
|
||||
for v in ctx.guild.voice_channels:
|
||||
if v.category == cat:
|
||||
await v.delete()
|
||||
await cat.delete()
|
||||
try:
|
||||
db[colName].delete_one({'role':r.id})
|
||||
except:
|
||||
pass
|
||||
await r.delete()
|
||||
|
||||
# reset command reset wed|sunaft|suneve|oneshot|all|other
|
||||
@client.command(name='reset', aliases=['deleteall','delall','cleargames','clear'], description='This deletes all games in a particular time slot category. This is a very powerful command. Be careful when you use it. The syntax is `reset {wed|sunaft|suneve|oneshot|other|all}`')
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def reset(ctx, *args):
|
||||
delList = []
|
||||
dbName = str(ctx.guild.id)
|
||||
db = dbClient[dbName]
|
||||
await ctx.channel.trigger_typing()
|
||||
for a in args:
|
||||
if a.lower() not in ['all', 'other', 'wed', 'sunaft', 'suneve', 'oneshot']:
|
||||
raise commands.CommandError(f'Invalid argument. {a} is not a valid flag for the command.')
|
||||
if a.lower() == 'all':
|
||||
for l in ['wed', 'sunaft', 'suneve', 'oneshot','other']:
|
||||
if l not in delList:
|
||||
delList.append(l)
|
||||
else:
|
||||
if a.lower() not in delList:
|
||||
delList.append(a.lower())
|
||||
for d in delList:
|
||||
colName = gameTime(d)
|
||||
try:
|
||||
cur = db[colName].find({})
|
||||
for g in cur:
|
||||
await ctx.guild.get_role(g['role']).delete()
|
||||
cat = ctx.guild.get_channel(g['category'])
|
||||
for c in cat.channels:
|
||||
await c.delete()
|
||||
await cat.delete()
|
||||
db[colName].deleteMany({})
|
||||
except:
|
||||
for r in ctx.guild.roles:
|
||||
if r.name.startswith(colName):
|
||||
for cat in ctx.guild.categories:
|
||||
if cat.name == r.name:
|
||||
for c in cat.channels:
|
||||
await c.delete()
|
||||
await cat.delete()
|
||||
await r.delete()
|
||||
await ctx.channel.send(f'All games for {colName} have been deleted.')
|
||||
|
||||
# Migrate Guild from Old Server Settings to New Settings
|
||||
@client.command(name='migrate', aliases=['migrategames','migratedata'], description='A set-up command to migrate games from the old server settings to the new server settings using the database for the first time.')
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def migrateData(ctx):
|
||||
await ctx.channel.trigger_typing()
|
||||
dbName = str(ctx.guild.id)
|
||||
db = dbClient[dbName]
|
||||
gNum = 0
|
||||
for r in ctx.guild.roles:
|
||||
if r.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
gNum += 1
|
||||
await r.edit(mentionable=True)
|
||||
gameName = r.name.split(': ',maxsplit=1)[1]
|
||||
colName = r.name.split(': ',maxsplit=1)[0]
|
||||
for c in ctx.guild.categories:
|
||||
if c.name == r.name:
|
||||
break
|
||||
permissions = c.overwrites
|
||||
for p in permissions:
|
||||
if isinstance(p,discord.Member) and permissions[p].manage_channels:
|
||||
break
|
||||
g = {
|
||||
'game': gameName,
|
||||
'gm': p.id,
|
||||
'capacity': 5,
|
||||
'category': c.id,
|
||||
'role': r.id
|
||||
}
|
||||
try:
|
||||
db[colName].replace_one({'role':g['role']}, g , upsert=True)
|
||||
except:
|
||||
raise commands.CommandError('Error: Database connection failed. Could not migrate games to database.')
|
||||
await ctx.channel.send(f'Finished migrating {gNum} games onto the database.')
|
||||
|
||||
# Import Cogs
|
||||
for cogfile in os.listdir('./cogs'):
|
||||
if cogfile.endswith('.py'):
|
||||
client.load_extension(f'cogs.{cogfile[:-3]}')
|
||||
|
||||
# Run Bot
|
||||
client.run(os.getenv('TEST_TOKEN'))
|
||||
client.run(os.getenv('TEST_3_TOKEN'))
|
@ -1,177 +0,0 @@
|
||||
import os
|
||||
import pymongo
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from bot import dbClient, p, state, gameTimes, gameTime, timeSlotList, dbFindTimeslot, dbLookupRole, in_game_channel
|
||||
|
||||
# Lookup GMs
|
||||
def gmLookup(guild, member):
|
||||
gamesList = []
|
||||
db = dbClient[str(guild.id)]
|
||||
try:
|
||||
for c in db.list_collection_names():
|
||||
ret = db[c].find({'gm':member.id})
|
||||
for e in ret:
|
||||
gamesList.append(guild.get_role(e['role']))
|
||||
except:
|
||||
for cat in guild.categories:
|
||||
if cat.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
for p in cat.overwrites:
|
||||
if cat.overwrites[member].manage_channels:
|
||||
for r in guild.roles:
|
||||
if r.name == cat.name:
|
||||
gamesList.append(r)
|
||||
break
|
||||
break
|
||||
finally:
|
||||
return gamesList
|
||||
|
||||
# Check if User is a GM
|
||||
def user_is_GM(ctx):
|
||||
if ctx.author.guild_permissions.administrator:
|
||||
return True
|
||||
if len(gmLookup(ctx.guild,ctx.author)) > 0:
|
||||
return True
|
||||
|
||||
# Check if User is a Player
|
||||
def user_is_Player(ctx):
|
||||
gamesList = []
|
||||
db = dbClient[str(ctx.guild.id)]
|
||||
try:
|
||||
for c in db.list_collection_names():
|
||||
ret = db[c].find({})
|
||||
for e in ret:
|
||||
gamesList.append(ctx.guild.get_role(e['role']))
|
||||
except:
|
||||
for r in ctx.guild.roles:
|
||||
if r.name.split(': ',maxsplit = 1)[0] in timeSlotList():
|
||||
gamesList.append(r)
|
||||
if set(gamesList) & set(ctx.author.roles):
|
||||
return True
|
||||
raise commands.CommandError('Error: You are not currently playing in any game.')
|
||||
|
||||
class GameManagement(commands.Cog, name='Game Management Commands'):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# GM Kick Command
|
||||
@commands.command(name='kickplayer', aliases=['kick','removeplayer','dropplayer','drop', 'remove'],description='This removes a player from your game. Can only be invoked by the GM or a server admin. The syntax is `kickplayer {@Player}`. **This command must be called inside the text channel of the game you are kicking the player from.**. *The action gets logged with the Committee so we can keep track of who is in wose game.*')
|
||||
@commands.check(user_is_GM)
|
||||
@commands.check(in_game_channel)
|
||||
async def kickPlayer(self, ctx, arg):
|
||||
if not (arg.startswith('<@') and not (arg.startswith('<@&'))):
|
||||
raise commands.CommandError('Invalid argument. The second parameter must @ the Player.')
|
||||
if not ctx.author.permissions_in(ctx.channel.category).manage_channels:
|
||||
raise commands.CommandError('You are not authorised to use this command here as you are not the GM.')
|
||||
await ctx.message.delete()
|
||||
await ctx.channel.trigger_typing()
|
||||
permissions = ctx.channel.category.overwrites
|
||||
for p in permissions:
|
||||
if isinstance(p,discord.Role) and p.name == ctx.channel.category.name:
|
||||
break
|
||||
u = await ctx.guild.fetch_member(int(arg.replace('<@', '').replace('>', '').replace('!', '')))
|
||||
await u.remove_roles(p)
|
||||
isPlayer = False
|
||||
for playerRoles in u.roles:
|
||||
if playerRoles.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
isPlayer = True
|
||||
break
|
||||
if not isPlayer:
|
||||
for r in ctx.guild.roles:
|
||||
if r.name == 'Players':
|
||||
break
|
||||
if r.name == 'Players':
|
||||
await u.remove_roles(r)
|
||||
for cr in ctx.guild.roles:
|
||||
if cr.name == 'Committee':
|
||||
break
|
||||
await ctx.channel.send(f'{u.mention} has been kicked from the game. This has been logged with the {cr.mention}.')
|
||||
for tc in ctx.guild.text_channels:
|
||||
if tc.name.split('-',maxsplit=1)[1] == 'moderator-logs':
|
||||
break
|
||||
await tc.send(f'Hey {cr.mention}, {u.mention} has been kicked from the {p.mention} game by GM {ctx.author.mention}.')
|
||||
|
||||
@kickPlayer.error
|
||||
async def clear_kick_error(self, ctx, error):
|
||||
if isinstance(error, commands.CheckFailure):
|
||||
await ctx.channel.send('You are not authorised to use this command as you are not a GM.')
|
||||
raise error
|
||||
|
||||
# GM Add Command
|
||||
@commands.command(name='addplayer', aliases=['add'],description='This command adds a player to your game. Can only be invoked by the GM for the game. The syntax is `addplayer {@Player} {@Game Role}`. As you cannot @-mention someone who cannot already see your channel, you are **not restricted** to use this command in a text channel belonging to your game. *The action gets logged with the Committee so we can keep track of who is in wose game.*')
|
||||
@commands.check(user_is_GM)
|
||||
async def addPlayer(self, ctx, arg1, arg2):
|
||||
if not (arg1.startswith('<@') and not (arg1.startswith('<@&'))):
|
||||
raise commands.CommandError('Invalid argument. The first parameter must @ the Player.')
|
||||
if not arg2.startswith('<@&'):
|
||||
raise commands.CommandError('Invalid argument. The second parameter must @ the game role.')
|
||||
r = ctx.guild.get_role(int(arg2[3:-1]))
|
||||
if r.name.split(': ',maxsplit=1)[0] not in timeSlotList():
|
||||
raise commands.CommandError('Error: the role is not a valid game role.')
|
||||
cat = dbLookupRole(ctx.guild,r)
|
||||
if not ctx.author.permissions_in(cat).manage_channels:
|
||||
raise commands.CommandError('You are not authorised to use this command as you are not the GM for the game.')
|
||||
await ctx.message.delete()
|
||||
await ctx.channel.trigger_typing()
|
||||
u = await ctx.guild.fetch_member(int(arg1.replace('<@', '').replace('>', '').replace('!', '')))
|
||||
for rl in ctx.guild.roles:
|
||||
if rl.name == 'Players':
|
||||
break
|
||||
await u.add_roles(r,rl)
|
||||
tPos = len(ctx.guild.channels)
|
||||
tFirst = None
|
||||
for t in cat.text_channels:
|
||||
if t.position <= tPos:
|
||||
tFirst = t
|
||||
tPos = t.position
|
||||
for cr in ctx.guild.roles:
|
||||
if cr.name == 'Committee':
|
||||
break
|
||||
for tc in ctx.guild.text_channels:
|
||||
if tc.name.split('-',maxsplit=1)[1] == 'moderator-logs':
|
||||
break
|
||||
await tFirst.send(f'{u.mention} has joined the game. Welcome! This has been logged with the {cr.mention}.')
|
||||
await ctx.channel.send(f'{u.mention} has been added to the game {r.mention}.')
|
||||
await tc.send(f'Hey {cr.mention}, {u.mention} was added to the {r.mention} game by {ctx.author.mention}.')
|
||||
|
||||
@addPlayer.error
|
||||
async def clear_add_error(self, ctx, error):
|
||||
if isinstance(error, commands.CheckFailure):
|
||||
await ctx.channel.send('You are not authorised to use this command as you are not a GM.')
|
||||
raise error
|
||||
|
||||
# Leave Game Command
|
||||
@commands.command(name='leave',aliases=['leavegame','quit','quitgame','dropout'],description='This command is to leave the game you are in. **It must be invoked in the text channel of the game you are in.** *The action gets logged with the Committee so we can keep track of who is in wose game.*')
|
||||
@commands.check(user_is_Player)
|
||||
@commands.check(in_game_channel)
|
||||
async def leaveGame(self, ctx):
|
||||
await ctx.message.delete()
|
||||
await ctx.channel.trigger_typing()
|
||||
permissions = ctx.channel.category.overwrites
|
||||
for p in permissions:
|
||||
if isinstance(p,discord.Role) and p.name == ctx.channel.category.name:
|
||||
break
|
||||
await ctx.author.remove_roles(p)
|
||||
isPlayer = False
|
||||
for playerRoles in ctx.author.roles:
|
||||
if playerRoles.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
isPlayer = True
|
||||
break
|
||||
if not isPlayer:
|
||||
for r in ctx.guild.roles:
|
||||
if r.name == 'Players':
|
||||
break
|
||||
if r.name == 'Players':
|
||||
await ctx.author.remove_roles(r)
|
||||
for cr in ctx.guild.roles:
|
||||
if cr.name == 'Committee':
|
||||
break
|
||||
await ctx.channel.send(f'{ctx.author.mention} has left the game. This has been logged with the {cr.mention}.')
|
||||
for tc in ctx.guild.text_channels:
|
||||
if tc.name.split('-',maxsplit=1)[1] == 'moderator-logs':
|
||||
break
|
||||
await tc.send(f'Hey {cr.mention}, {ctx.author.mention} has left the {p.mention} game by GM {ctx.author.mention}.')
|
||||
|
||||
# Cog Setup Function
|
||||
def setup(client):
|
||||
client.add_cog(GameManagement(client))
|
@ -1,48 +0,0 @@
|
||||
import discord
|
||||
from discord.ext import commands, tasks
|
||||
from datetime import datetime
|
||||
|
||||
def helpChannels(client):
|
||||
l = []
|
||||
for guild in client.guilds:
|
||||
channel = discord.utils.find(lambda c: c.name == '⛑-help', guild.channels)
|
||||
l.append(channel)
|
||||
return l
|
||||
|
||||
def committeeRoles(client):
|
||||
l = []
|
||||
for guild in client.guilds:
|
||||
role = discord.utils.find(lambda r: r.name == 'Committee', guild.roles)
|
||||
l.append(role)
|
||||
return l
|
||||
|
||||
def checkCommitteeRoles(author,committee):
|
||||
if set(author.roles) & set(committee):
|
||||
return True
|
||||
|
||||
class HelpNotifier(commands.Cog, name='Help Notifier Commands'):
|
||||
def __init__(self,client):
|
||||
self.client = client
|
||||
|
||||
# Message in Help channel event listener.
|
||||
@commands.Cog.listener()
|
||||
async def on_message(self,message):
|
||||
if message.author.bot:
|
||||
return
|
||||
if checkCommitteeRoles(message.author, committeeRoles(self.client)):
|
||||
return
|
||||
if message.channel not in helpChannels(self.client):
|
||||
return
|
||||
committeeChannel = discord.utils.find(lambda t: t.name == '🗞-moderator-logs',message.guild.channels)
|
||||
committeeRole = discord.utils.find(lambda c: c.name == 'Committee', message.guild.roles)
|
||||
embed = discord.Embed(
|
||||
title = message.content,
|
||||
description = f'[Jump to Message]({message.jump_url})',
|
||||
colour = discord.Colour.orange(),
|
||||
)
|
||||
embed.set_footer(text=datetime.now().strftime('%a %-d %b %y, %-I:%M %p'))
|
||||
embed.set_author(name=message.author.display_name, icon_url=message.author.avatar_url)
|
||||
await committeeChannel.send(f'Hey {committeeRole.mention}, {message.author.mention} has just posted the following message in the {message.channel.mention} channel', embed=embed)
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(HelpNotifier(client))
|
@ -1,112 +0,0 @@
|
||||
import discord
|
||||
from discord.ext import commands, tasks
|
||||
from datetime import datetime
|
||||
from bot import dbClient, p, state, gameTimes, gameTime, timeSlotList, dbFindTimeslot, dbLookupRole
|
||||
|
||||
def gameCategories(client):
|
||||
l = []
|
||||
try:
|
||||
for guild in client.guilds:
|
||||
dbName = str(guild.id)
|
||||
db = dbClient[dbName]
|
||||
for colName in db.list_collection_names():
|
||||
if colName != 'settings':
|
||||
ret = db[colName].find()
|
||||
for e in ret:
|
||||
l.append(guild.get_channel(e['category']))
|
||||
except:
|
||||
for guild in client.guilds:
|
||||
for cat in guild.categories:
|
||||
if cat.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
l.append(cat)
|
||||
return l
|
||||
|
||||
def gameRoles(client):
|
||||
l = []
|
||||
try:
|
||||
for guild in client.guilds:
|
||||
dbName = str(guild.id)
|
||||
db = dbClient[dbName]
|
||||
for colName in db.list_collection_names():
|
||||
if colName != 'settings':
|
||||
ret = db[colName].find()
|
||||
for e in ret:
|
||||
l.append(guild.get_role(e['role']))
|
||||
except:
|
||||
for guild in client.guilds:
|
||||
for role in guild.roles:
|
||||
if role.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
l.append(role)
|
||||
return l
|
||||
|
||||
def committeeRoles(client):
|
||||
l = []
|
||||
for guild in client.guilds:
|
||||
role = discord.utils.find(lambda r: r.name == 'Committee', guild.roles)
|
||||
l.append(role)
|
||||
return l
|
||||
|
||||
def memberRoles(client):
|
||||
l = []
|
||||
for guild in client.guilds:
|
||||
role = discord.utils.find(lambda r: r.name == 'Life Members', guild.roles)
|
||||
l.append(role)
|
||||
role = discord.utils.find(lambda r: r.name == 'Members: Full Year', guild.roles)
|
||||
l.append(role)
|
||||
role = discord.utils.find(lambda r: r.name == 'Members: Semester 2', guild.roles)
|
||||
l.append(role)
|
||||
role = discord.utils.find(lambda r: r.name == 'Members: Semester 1', guild.roles)
|
||||
l.append(role)
|
||||
role = discord.utils.find(lambda r: r.name == 'New Member', guild.roles)
|
||||
l.append(role)
|
||||
role = discord.utils.find(lambda r: r.name == 'Temporary Access', guild.roles)
|
||||
l.append(role)
|
||||
return l
|
||||
|
||||
def checkCommitteeRoles(author,committee):
|
||||
if set(author.roles) & set(committee):
|
||||
return True
|
||||
|
||||
def checkMemberRoles(author,memberRoles):
|
||||
if set(author.roles) & set(memberRoles):
|
||||
return True
|
||||
|
||||
class MembershipRestriction(commands.Cog, name='Membership Restriction Protocol'):
|
||||
def __init__(self,client):
|
||||
self.client = client
|
||||
|
||||
# Event Listener for Message from Non-Member in Game Channels
|
||||
@commands.Cog.listener()
|
||||
async def on_message(self,message):
|
||||
if message.author.bot:
|
||||
return
|
||||
guestRole = discord.utils.find(lambda g: g.name == 'Guest', message.guild.roles)
|
||||
if guestRole.permissions.read_messages:
|
||||
return
|
||||
if message.channel.category not in gameCategories(self.client):
|
||||
return
|
||||
if checkCommitteeRoles(message.author, committeeRoles(self.client)) or message.guild.owner == message.author:
|
||||
return
|
||||
if checkMemberRoles(message.author, memberRoles(self.client)):
|
||||
return
|
||||
signupChannel = discord.utils.find(lambda c: c.name == '📋-membership-signups', message.guild.channels)
|
||||
if message.channel.overwrites_for(message.author).manage_channels:
|
||||
return
|
||||
await message.channel.send(f'{message.author.mention} does not have a verified membership of Geas. Please submit your membership confirmation for verification in the {signupChannel.mention} to ensure you have access to your game.')
|
||||
await message.channel.category.set_permissions(message.author, send_messages = False, connect = False)
|
||||
await message.delete()
|
||||
|
||||
# Event Listener for Reinstating Permissions when Membership is Assigned
|
||||
@commands.Cog.listener()
|
||||
async def on_member_update(self,before,after):
|
||||
if before.roles == after.roles:
|
||||
return
|
||||
if checkMemberRoles(after, memberRoles(self.client)):
|
||||
for g in after.roles:
|
||||
if g in gameRoles(self.client):
|
||||
cat = dbLookupRole(after.guild, g)
|
||||
if not cat.overwrites_for(after).send_messages and not cat.overwrites_for(after).manage_channels:
|
||||
await cat.set_permissions(after, overwrite = None)
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(MembershipRestriction(client))
|
@ -1,142 +0,0 @@
|
||||
import discord
|
||||
from discord.ext import commands, tasks
|
||||
from datetime import datetime
|
||||
|
||||
def membershipSignupChannels(client):
|
||||
l = []
|
||||
for guild in client.guilds:
|
||||
channel = discord.utils.find(lambda c: c.name == '📋-membership-signups', guild.channels)
|
||||
l.append(channel)
|
||||
return l
|
||||
|
||||
def committeeRoles(client):
|
||||
l = []
|
||||
for guild in client.guilds:
|
||||
role = discord.utils.find(lambda r: r.name == 'Committee', guild.roles)
|
||||
l.append(role)
|
||||
return l
|
||||
|
||||
def checkCommitteeRoles(author,committee):
|
||||
if set(author.roles) & set(committee):
|
||||
return True
|
||||
|
||||
class MembershipVerification(commands.Cog, name='Membership Verification Commands'):
|
||||
def __init__(self,client):
|
||||
self.client = client
|
||||
|
||||
# Message in Membership Signup event listener.
|
||||
@commands.Cog.listener()
|
||||
async def on_message(self,message):
|
||||
if message.channel in membershipSignupChannels(self.client) and message.author.id != self.client.user.id:
|
||||
if message.attachments == []:
|
||||
await message.author.send(f'**Error**: The message you posted in the {message.channel.name} channel of {message.guild.name} was invalid. Your post must contain a screensot of your proof of purchase for membership from EUSA.')
|
||||
await message.delete()
|
||||
return
|
||||
await message.add_reaction('1️⃣')
|
||||
await message.add_reaction('2️⃣')
|
||||
await message.add_reaction('📅')
|
||||
await message.add_reaction('📚')
|
||||
await message.add_reaction('⚠️')
|
||||
await message.add_reaction('🚫')
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_raw_reaction_add(self,payload):
|
||||
if payload.user_id != self.client.user.id and self.client.get_channel(payload.channel_id) in membershipSignupChannels(self.client):
|
||||
guild = await self.client.fetch_guild(payload.guild_id)
|
||||
member = await guild.fetch_member(payload.user_id)
|
||||
channel = await self.client.fetch_channel(payload.channel_id)
|
||||
message = await channel.fetch_message(payload.message_id)
|
||||
studentsRole = discord.utils.find(lambda g: g.name == 'Students', guild.roles)
|
||||
semesterOneRole = discord.utils.find(lambda g: g.name == 'Members: Semester 1', guild.roles)
|
||||
semesterTwoRole = discord.utils.find(lambda g: g.name == 'Members: Semester 2', guild.roles)
|
||||
fullYearRole = discord.utils.find(lambda g: g.name == 'Members: Full Year', guild.roles)
|
||||
channels = await guild.fetch_channels()
|
||||
committeeChannel = discord.utils.find(lambda t: t.name == '🗞-moderator-logs', channels)
|
||||
committeeRole = discord.utils.find(lambda c: c.name == 'Committee', guild.roles)
|
||||
if not checkCommitteeRoles(member, committeeRoles(self.client)):
|
||||
await member.send(f'**Error**: Only Committee members are authorised to react to posts on the {channel.name} channel for {guild.name}.')
|
||||
await message.remove_reaction(payload.emoji.name, member)
|
||||
return
|
||||
if payload.emoji.name == '1️⃣':
|
||||
await message.author.add_roles(semesterOneRole)
|
||||
await message.add_reaction('✅')
|
||||
await message.author.send(f'Your membership for {guild.name} has been verified and you have been assigned the role **Members: Semester 1**.')
|
||||
return
|
||||
if payload.emoji.name == '2️⃣':
|
||||
await message.author.add_roles(semesterTwoRole)
|
||||
await message.add_reaction('✅')
|
||||
await message.author.send(f'Your membership for {guild.name} has been verified and you have been assigned the role **Members: Semester 2**.')
|
||||
return
|
||||
if payload.emoji.name == '📅':
|
||||
await message.author.add_roles(fullYearRole)
|
||||
await message.add_reaction('✅')
|
||||
await message.author.send(f'Your membership for {guild.name} has been verified and you have been assigned the role **Members: Full Year**.')
|
||||
return
|
||||
if payload.emoji.name == '📚':
|
||||
await message.author.add_roles(studentsRole)
|
||||
await message.author.send(f'You have additionally been assigned the role **Students**.')
|
||||
return
|
||||
if payload.emoji.name == '⚠️':
|
||||
embed = discord.Embed(
|
||||
title = message.author.name,
|
||||
description = f'[Jump to Message]({message.jump_url})',
|
||||
colour = discord.Colour.orange(),
|
||||
)
|
||||
await message.author.send(f'Your membership for {guild.name} needs to be reviewed by a Committee member.')
|
||||
await committeeChannel.send(f'Hey {committeeRole.mention}, there is a problem verifying the membership of {message.author.mention}.\nCould someone verify this person\'s membership manually via the EUSA portal and return to the message?', embed=embed)
|
||||
return
|
||||
if payload.emoji.name == '🚫':
|
||||
embed = discord.Embed(
|
||||
title = message.author.name,
|
||||
description = f'[Jump to Message]({message.jump_url})',
|
||||
colour = discord.Colour.red(),
|
||||
)
|
||||
await message.author.send(f'Your membership for {guild.name} could not be verified. Please make sure that your name and the kind of membership you have bought are visible in the screenshot you upload. Please contact a Committee member if you have any difficulties.')
|
||||
await committeeChannel.send(f'Hey {committeeRole.mention}, verifying the membership of {message.author.mention} failed.', embed=embed)
|
||||
return
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_raw_reaction_remove(self,payload):
|
||||
if payload.user_id != self.client.user.id and self.client.get_channel(payload.channel_id) in membershipSignupChannels(self.client):
|
||||
guild = await self.client.fetch_guild(payload.guild_id)
|
||||
member = await guild.fetch_member(payload.user_id)
|
||||
channel = await self.client.fetch_channel(payload.channel_id)
|
||||
message = await channel.fetch_message(payload.message_id)
|
||||
studentsRole = discord.utils.find(lambda g: g.name == 'Students', guild.roles)
|
||||
semesterOneRole = discord.utils.find(lambda g: g.name == 'Members: Semester 1', guild.roles)
|
||||
semesterTwoRole = discord.utils.find(lambda g: g.name == 'Members: Semester 2', guild.roles)
|
||||
fullYearRole = discord.utils.find(lambda g: g.name == 'Members: Full Year', guild.roles)
|
||||
channels = await guild.fetch_channels()
|
||||
committeeChannel = discord.utils.find(lambda t: t.name == '🗞-moderator-logs', channels)
|
||||
committeeRole = discord.utils.find(lambda c: c.name == 'Committee', guild.roles)
|
||||
if not checkCommitteeRoles(member, committeeRoles(self.client)):
|
||||
await message.remove_reaction(payload.emoji.name, member)
|
||||
return
|
||||
if payload.emoji.name == '1️⃣':
|
||||
await message.author.remove_roles(semesterOneRole)
|
||||
await message.remove_reaction('✅',self.client.user)
|
||||
await message.author.send(f'Your role **Members: Semester 1** for {guild.name} has been removed.')
|
||||
return
|
||||
if payload.emoji.name == '2️⃣':
|
||||
await message.author.remove_roles(semesterTwoRole)
|
||||
await message.remove_reaction('✅',self.client.user)
|
||||
await message.author.send(f'Your role **Members: Semester 2** for {guild.name} has been removed.')
|
||||
return
|
||||
if payload.emoji.name == '📅':
|
||||
await message.author.remove_roles(fullYearRole)
|
||||
await message.remove_reaction('✅',self.client.user)
|
||||
await message.author.send(f'Your role **Members: Full Year** for {guild.name} has been removed.')
|
||||
return
|
||||
if payload.emoji.name == '📚':
|
||||
await message.author.remove_roles(studentsRole)
|
||||
await message.author.send(f'Your role **Students** for {guild.name} has been removed.')
|
||||
return
|
||||
if payload.emoji.name == '⚠️':
|
||||
await message.author.send(f'Your membership for {guild.name} is being reviewed by a Committee member.')
|
||||
return
|
||||
if payload.emoji.name == '🚫':
|
||||
await message.author.send(f'Your membership for {guild.name} is being reviewed by a Committee member.')
|
||||
return
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(MembershipVerification(client))
|
@ -1,190 +0,0 @@
|
||||
import os
|
||||
import pymongo
|
||||
import discord
|
||||
from discord.ext import commands, tasks
|
||||
from bot import dbClient, p, state, gameTimes, gameTime, timeSlotList, dbFindTimeslot, dbLookupRole, syncGames
|
||||
|
||||
pitchState = {}
|
||||
|
||||
def pitchListening():
|
||||
l = []
|
||||
for guild in pitchState:
|
||||
for slot in pitchState[guild]:
|
||||
l.append(pitchState[guild][slot]['menuMessage'].id)
|
||||
return l
|
||||
|
||||
|
||||
emojiList = [
|
||||
'1️⃣',
|
||||
'2️⃣',
|
||||
'3️⃣',
|
||||
'4️⃣',
|
||||
'5️⃣',
|
||||
'6️⃣',
|
||||
'7️⃣',
|
||||
'8️⃣',
|
||||
'9️⃣',
|
||||
'🔟',
|
||||
'🇦',
|
||||
'🇧',
|
||||
'🇨',
|
||||
'🇩',
|
||||
'🇪',
|
||||
'🇫',
|
||||
'🇬',
|
||||
'🇭',
|
||||
'🇮',
|
||||
'🇯'
|
||||
]
|
||||
|
||||
class PitchMenu(commands.Cog, name='Pitch Menu Commands'):
|
||||
def __init__(self,client):
|
||||
self.client = client
|
||||
|
||||
# Pitch Run Command
|
||||
@commands.has_permissions(administrator=True)
|
||||
@commands.group(name='pitch', aliases=['pitches'], description='The command to run pitches. It has two subcommands. Syntax: `pitch {run|clear} {wed|sunaft|suneve|oneshot|other}`')
|
||||
async def pitch(self, ctx):
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send(f'Invalid subcommand. Please use either `{p}pitch run` or `{p}pitch clear`')
|
||||
|
||||
@pitch.command(name='run', aliases=['start','generate','setup'], description='Subcommand to set up pitches. Syntax: `pitch run {wed|sunaft|suneve|oneshot|other}`')
|
||||
async def pitch_run(self, ctx, arg):
|
||||
if arg.lower() not in ['all', 'other', 'wed', 'sunaft', 'suneve', 'oneshot']:
|
||||
raise commands.CommandError('Invalid argument. {arg} is not a valid flag for the command.')
|
||||
syncGames(ctx.guild)
|
||||
await ctx.message.delete()
|
||||
await ctx.channel.trigger_typing()
|
||||
## Constructing Pitch State Dictionary
|
||||
dbName = str(ctx.guild.id)
|
||||
db = dbClient[dbName]
|
||||
colName = gameTime(arg.lower())
|
||||
if dbName not in pitchState:
|
||||
pitchState[dbName] = {}
|
||||
if colName not in pitchState[dbName]:
|
||||
pitchState[dbName][colName] = {}
|
||||
pitchState[dbName][colName]['entries'] = []
|
||||
# Try database queries
|
||||
try:
|
||||
cur = db[colName].find().sort('game')
|
||||
for entry in cur:
|
||||
gameDict = {}
|
||||
gameDict['game'] = entry['game']
|
||||
gameDict['gm'] = await ctx.guild.fetch_member(entry['gm'])
|
||||
gameDict['role'] = discord.utils.find(lambda m: m.id == entry['role'],ctx.guild.roles)
|
||||
gameDict['capacity'] = entry['capacity'] if entry['capacity'] != None else 5
|
||||
gameDict['signups'] = 0
|
||||
cat = discord.utils.find(lambda m: m.id == entry['category'], ctx.guild.categories)
|
||||
tFirst = None
|
||||
tPos = len(ctx.guild.channels)
|
||||
for t in cat.text_channels:
|
||||
if t.position <= tPos:
|
||||
tFirst = t
|
||||
tPos = t.position
|
||||
gameDict['textchannel'] = tFirst
|
||||
pitchState[dbName][colName]['entries'].append(dict(gameDict))
|
||||
# Infer from server if database fails
|
||||
except:
|
||||
for r in ctx.guild.roles:
|
||||
if r.name.startswith(colName):
|
||||
gameDict = {}
|
||||
gameDict['game'] = r.name.split(': ',maxsplit=1)[1]
|
||||
gameDict['role'] = r
|
||||
gameDict['capacity'] = 5
|
||||
gameDict['signups'] = 0
|
||||
cat = discord.utils.find(lambda m: m.name == r.name, ctx.guild.categories)
|
||||
for p in cat.overwrites:
|
||||
if isinstance(p,discord.Member) and cat.overwrites[p].manage_channels:
|
||||
gameDict['gm'] = p
|
||||
break
|
||||
tFirst = None
|
||||
tPos = len(ctx.guild.channels)
|
||||
for t in cat.text_channels:
|
||||
if t.position <= tPos:
|
||||
tFirst = t
|
||||
tPos = t.position
|
||||
gameDict['textchannel'] = t
|
||||
pitchState[dbName][colName]['entries'].append(dict(gameDict))
|
||||
pitchState[dbName][colName]['entries'].sort(key= lambda m: m['game'])
|
||||
# Begin Constructing the Menu
|
||||
pitchState[dbName][colName]['headerMessage'] = await ctx.channel.send(f'**Game listing for {colName}**\n_ _\nThe following are the games that are being pitched. Please select your game by clicking on the emoji reaction at the bottom of the menu.\n_ _')
|
||||
for e in pitchState[dbName][colName]['entries']:
|
||||
e['message'] = await ctx.channel.send(f'{emojiList[pitchState[dbName][colName]["entries"].index(e)]} **{e["game"]}** (GM {e["gm"].mention}). {e["capacity"] - e["signups"]} spaces remaining.')
|
||||
pitchState[dbName][colName]['menuMessage'] = await ctx.channel.send('_ _\n**Please select a game from the above list by clicking on the corresponding emoji reaction below.**')
|
||||
for option in pitchState[dbName][colName]['entries']:
|
||||
await pitchState[dbName][colName]['menuMessage'].add_reaction(emojiList[pitchState[dbName][colName]["entries"].index(option)])
|
||||
|
||||
@pitch.command(name='clear', aliases=['end','cancel','reset','delete'], description='Subcommand to clear pitches. {wed|sunaft|suneve|oneshot|other}')
|
||||
async def pitch_clear(self, ctx, arg):
|
||||
if arg.lower() not in ['all', 'other', 'wed', 'sunaft', 'suneve', 'oneshot']:
|
||||
raise commands.CommandError(f'Invalid argument. {arg} is not a valid flag for the command.')
|
||||
await ctx.message.delete()
|
||||
await ctx.channel.trigger_typing()
|
||||
dbName = str(ctx.guild.id)
|
||||
db = dbClient[dbName]
|
||||
colName = gameTime(arg.lower())
|
||||
for e in pitchState[dbName][colName]['entries']:
|
||||
await e['message'].delete()
|
||||
await pitchState[dbName][colName]['menuMessage'].delete()
|
||||
await pitchState[dbName][colName]['headerMessage'].delete()
|
||||
await ctx.send(f'Pitch menu for {colName} has been reset.')
|
||||
pitchState[dbName][colName].clear
|
||||
|
||||
# Emoji Reaction Event Listeners
|
||||
@commands.Cog.listener()
|
||||
async def on_raw_reaction_add(self, payload):
|
||||
if payload.user_id != self.client.user.id:
|
||||
if payload.message_id in pitchListening():
|
||||
guildID = str(payload.guild_id)
|
||||
guild = discord.utils.find(lambda g: g.id == payload.guild_id, self.client.guilds)
|
||||
channel = discord.utils.find(lambda c: c.id == payload.channel_id, guild.channels)
|
||||
author = await guild.fetch_member(payload.user_id)
|
||||
for slot in pitchState[guildID]:
|
||||
if pitchState[guildID][slot]['menuMessage'].id == payload.message_id:
|
||||
break
|
||||
message = await channel.fetch_message(pitchState[guildID][slot]['menuMessage'].id)
|
||||
for reaction in message.reactions:
|
||||
if reaction.emoji != payload.emoji.name:
|
||||
i = emojiList.index(reaction.emoji)
|
||||
if author in await reaction.users().flatten():
|
||||
await reaction.remove(author)
|
||||
i = emojiList.index(payload.emoji.name)
|
||||
e = pitchState[guildID][slot]['entries'][i]
|
||||
playerRole = discord.utils.find(lambda p: p.name == 'Players',guild.roles)
|
||||
await author.add_roles(playerRole,e['role'])
|
||||
e['signups'] += 1
|
||||
contentString = f'{emojiList[i]} **{e["game"]}** (GM {e["gm"].mention}). {e["capacity"] - e["signups"] if e["signups"] <= e["capacity"] else 0} {"space" if e["capacity"] - e["signups"] == 1 else "spaces"} remaining.'
|
||||
await e['message'].edit(content=f'~~{contentString}~~' if e['signups'] >= e['capacity'] else contentString)
|
||||
await e['textchannel'].send(f'{author.mention} has joined the game.')
|
||||
|
||||
# Emoji Un-React Event Listener
|
||||
@commands.Cog.listener()
|
||||
async def on_raw_reaction_remove(self, payload):
|
||||
if payload.user_id != self.client.user.id:
|
||||
if payload.message_id in pitchListening():
|
||||
guildID = str(payload.guild_id)
|
||||
guild = discord.utils.find(lambda g: g.id == payload.guild_id, self.client.guilds)
|
||||
channel = discord.utils.find(lambda c: c.id == payload.channel_id, guild.channels)
|
||||
author = await guild.fetch_member(payload.user_id)
|
||||
for slot in pitchState[guildID]:
|
||||
if pitchState[guildID][slot]['menuMessage'].id == payload.message_id:
|
||||
break
|
||||
message = await channel.fetch_message(pitchState[guildID][slot]['menuMessage'].id)
|
||||
i = emojiList.index(payload.emoji.name)
|
||||
e = pitchState[guildID][slot]['entries'][i]
|
||||
e['signups'] -= 1
|
||||
contentString = f'{emojiList[i]} **{e["game"]}** (GM {e["gm"].mention}). {e["capacity"] - e["signups"] if e["signups"] <= e["capacity"] else 0} {"space" if e["capacity"] - e["signups"] == 1 else "spaces"} remaining.'
|
||||
await e['message'].edit(content=f'~~{contentString}~~' if e['signups'] >= e['capacity'] else contentString)
|
||||
await e['textchannel'].send(f'{author.mention} has left the game.')
|
||||
await author.remove_roles(e['role'])
|
||||
isPlayer = False
|
||||
for role in author.roles:
|
||||
if role.name.split(': ',maxsplit=1)[0] in timeSlotList():
|
||||
isPlayer = True
|
||||
break
|
||||
if not isPlayer:
|
||||
playerRole = discord.utils.find(lambda p: p.name == 'Players',guild.roles)
|
||||
await author.remove_roles(playerRole)
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(PitchMenu(client))
|
@ -2,7 +2,7 @@ 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 dev import configFile, yaml_load, yaml_dump
|
||||
from bot import configFile, yaml_load, yaml_dump
|
||||
|
||||
### Cog for handling the non-Slash prefix for native Bot commands.
|
||||
|
@ -7,10 +7,10 @@ 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 dev import loadCog, unloadCog
|
||||
from bot import loadCog, unloadCog
|
||||
|
||||
##### Debug Cog
|
||||
class DevCog(commands.Cog):
|
||||
##### Dev Cog
|
||||
class Dev(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
@ -28,4 +28,4 @@ class DevCog(commands.Cog):
|
||||
await ctx.reply(f'Debug commands disabled.')
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(DevCog(client))
|
||||
client.add_cog(Dev(client))
|
@ -6,7 +6,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
|
||||
import logging
|
||||
# logger and handler
|
||||
from dev import configFile, yaml_load, yaml_dump
|
||||
from bot import configFile, yaml_load, yaml_dump
|
||||
|
||||
#### Actions for the Bot to take on connecting to Discord.
|
||||
class on_connect(commands.Cog):
|
@ -6,7 +6,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
|
||||
import logging
|
||||
# logger and handler
|
||||
from dev import configFile, yaml_load, yaml_dump
|
||||
from bot import configFile, yaml_load, yaml_dump
|
||||
|
||||
##### Actions for the bot to take whenever a channel in a guild is deleted
|
||||
class on_guild_channel_delete(commands.Cog):
|
@ -6,7 +6,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
|
||||
import logging
|
||||
# logger and handler
|
||||
from dev import configFile, setConfig, yaml_load, yaml_dump
|
||||
from bot import configFile, setConfig, yaml_load, yaml_dump
|
||||
|
||||
#### Actions for the bot to take when the Bot joins a guild.
|
||||
|
@ -6,7 +6,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
|
||||
import logging
|
||||
# logger and handler
|
||||
from dev import clearConfig, configFile, yaml_load, yaml_dump
|
||||
from bot import clearConfig, configFile, yaml_load, yaml_dump
|
||||
|
||||
#### Actions for the bot to take when removed from a guild.
|
||||
|
@ -6,7 +6,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
|
||||
import logging
|
||||
# logger and handler
|
||||
from dev import configFile, yaml_load, yaml_dump
|
||||
from bot import configFile, yaml_load, yaml_dump
|
||||
|
||||
##### Actions for the bot to take whenever there is a new role created.
|
||||
|
@ -6,7 +6,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
|
||||
import logging
|
||||
# logger and handler
|
||||
from dev import configFile, yaml_load, yaml_dump
|
||||
from bot import configFile, yaml_load, yaml_dump
|
||||
|
||||
##### Actions for the bot to take whenever there is a new role deleted.
|
||||
|
@ -6,7 +6,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
|
||||
import logging
|
||||
# logger and handler
|
||||
from dev import configFile, yaml_load, yaml_dump
|
||||
from bot import configFile, yaml_load, yaml_dump
|
||||
|
||||
##### Actions for the bot to take whenever there is a new role deleted.
|
||||
|
@ -6,7 +6,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
|
||||
import logging
|
||||
# logger and handler
|
||||
from dev import configFile, yaml_load, yaml_dump
|
||||
from bot import configFile, yaml_load, yaml_dump
|
||||
|
||||
##### Actions for the bot to take whenever the guild info or ownership are updated.
|
||||
class on_guild_update(commands.Cog):
|
@ -7,7 +7,7 @@ from discord_slash import SlashCommand, SlashContext, cog_ext, utils # Slash C
|
||||
from discord_slash.utils.manage_commands import create_choice, create_option # Slash Command features
|
||||
import logging
|
||||
# logger and handler
|
||||
from dev import clearConfig, configFile, setConfig, yaml_dump, yaml_load
|
||||
from bot import clearConfig, configFile, setConfig, yaml_dump, yaml_load
|
||||
|
||||
#### Actions for the Bot to take once it is ready to interact with commands.
|
||||
class on_ready(commands.Cog):
|
@ -7,7 +7,7 @@ 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 dev import clearConfig, configFile, loadCog, loadCogs, setConfig, unloadCog, unloadCogs, yaml_dump, yaml_load
|
||||
from bot import clearConfig, configFile, loadCog, loadCogs, setConfig, unloadCog, unloadCogs, yaml_dump, yaml_load
|
||||
|
||||
##### Debug Cog
|
||||
class Debug(commands.Cog):
|
||||
|
136
app/dev.py
136
app/dev.py
@ -1,136 +0,0 @@
|
||||
import os, sys # OS Locations
|
||||
from dotenv import load_dotenv # Import OS variables from Dotenv file.
|
||||
load_dotenv() # Load Dotenv. Delete this for production
|
||||
import yaml # Parser for yaml files for config settings.
|
||||
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
|
||||
|
||||
## Define YAML functions
|
||||
|
||||
def yaml_load(filepath:str):
|
||||
### Loads a YAML file
|
||||
with open(filepath, 'r') as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
return data
|
||||
|
||||
def yaml_dump(data:dict, filepath:str):
|
||||
### Dumps a YAML file
|
||||
with open(filepath, 'w') as file:
|
||||
yaml.dump(data, file)
|
||||
|
||||
# Locate or create config file
|
||||
configFile = './data/config.yml'
|
||||
|
||||
if not os.path.exists(configFile):
|
||||
yaml_dump({},configFile)
|
||||
|
||||
# Locate or create data file
|
||||
dataFile = './data/data.yml'
|
||||
|
||||
if not os.path.exists(dataFile):
|
||||
yaml_dump({},dataFile)
|
||||
|
||||
# Locate Cogs Directory
|
||||
cogsDir = 'dev_cogs'
|
||||
|
||||
# --> Temporary disable logging because of verboseness.
|
||||
# ## Logging configuration imported boilerplate from Discord Py Docs
|
||||
# logger = logging.getLogger('discord')
|
||||
# logger.setLevel(logging.DEBUG)
|
||||
# handler = logging.FileHandler(filename='discord.log', encoding='utf-8', mode='w')
|
||||
# handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
|
||||
# logger.addHandler(handler)
|
||||
|
||||
#### Dynamic Prefixes
|
||||
def getPrefix(client, message):
|
||||
conf = yaml_load(configFile)
|
||||
return conf[str(message.guild.id)]['prefix']
|
||||
|
||||
# Define Clients
|
||||
client = commands.Bot(
|
||||
intents=discord.Intents.all(),
|
||||
command_prefix=getPrefix
|
||||
)
|
||||
slash = SlashCommand(
|
||||
client,
|
||||
sync_commands = True,
|
||||
sync_on_cog_reload = True
|
||||
)
|
||||
# sync_on_reload is an important parameter that will become relevant when having to reload cogs on changing bot configs.
|
||||
|
||||
# Define Config keys
|
||||
configKeys = ['adminroles', 'committeerole', 'botrole', 'modchannel', 'name', 'owner', 'prefix']
|
||||
|
||||
def setConfig(guild:discord.Guild):
|
||||
#### Check if the bot is missing any config entries for the guilds it is in, and if it is then add it in.
|
||||
#### N.B.: The way the commands work, the bot will have to list specific guilds in which it will synchronise the commands when it is defining them. So it needs to give a list of all the guilds it is part of when the bot loads, which it draws from the config files.
|
||||
#### Because the bot connects to Discord after it loads, it will not be able to introspect and see what guilds it is part of before the commands are first loaded, and it will only add new guilds to the config files after it has already connected.
|
||||
#### The Bot will first need to set up all of its configurations, and then begin loading all other commands once it is ready.
|
||||
conf = yaml_load(configFile)
|
||||
if str(guild.id) not in conf:
|
||||
conf[str(guild.id)] = {}
|
||||
if 'name' not in conf[str(guild.id)] or conf[str(guild.id)]['name'] != guild.name:
|
||||
conf[str(guild.id)]['name'] = guild.name
|
||||
if 'owner' not in conf[str(guild.id)] or conf[str(guild.id)]['owner'] != guild.owner_id:
|
||||
conf[str(guild.id)]['owner'] = guild.owner_id
|
||||
if 'adminroles' not in conf[str(guild.id)] or (type(conf[str(guild.id)]['adminroles']) is not list or len(conf[str(guild.id)]['adminroles']) == 0 or None in conf[str(guild.id)]['adminroles']):
|
||||
conf[str(guild.id)]['adminroles'] = []
|
||||
for role in guild.roles:
|
||||
if not (role.is_bot_managed() or role.is_integration()) and role.permissions.administrator:
|
||||
conf[str(guild.id)]['adminroles'].append(role.id)
|
||||
if 'prefix' not in conf[str(guild.id)]:
|
||||
conf[str(guild.id)]['prefix'] = '-'
|
||||
if 'modchannel' not in conf[str(guild.id)]:
|
||||
if guild.system_channel is None:
|
||||
p = len(guild.channels)
|
||||
c = None
|
||||
for t in guild.text_channels:
|
||||
if t.position < p:
|
||||
p = t.position
|
||||
conf[str(guild.id)]['modchannel'] = t.id
|
||||
else:
|
||||
conf[str(guild.id)]['modchannel'] = guild.system_channel.id
|
||||
yaml_dump(conf, configFile)
|
||||
|
||||
def clearConfig(guildKey:str):
|
||||
#### Delete Configs for Guilds that the Bot is no longer in
|
||||
conf = yaml_load(configFile)
|
||||
if discord.utils.find(lambda g: str(g.id) == guildKey, client.guilds) is None:
|
||||
del conf[guildKey]
|
||||
yaml_dump(conf, configFile)
|
||||
|
||||
def loadCog(filepath:str):
|
||||
path = os.path.normpath(filepath).split(os.path.sep)
|
||||
if path[-1].endswith('.py'):
|
||||
path[-1] = path[-1][:-3]
|
||||
client.load_extension('.'.join(path))
|
||||
|
||||
def unloadCog(filepath:str):
|
||||
path = os.path.normpath(filepath).split(os.path.sep)
|
||||
if path[-1].endswith('.py'):
|
||||
path[-1] = path[-1][:-3]
|
||||
client.unload_extension('.'.join(path))
|
||||
|
||||
def loadCogs(cogClass:str = 'all'):
|
||||
for category in os.listdir(f'./{cogsDir}'):
|
||||
if cogClass == 'all' or cogClass == category:
|
||||
for cogfile in os.listdir(f'./{cogsDir}/{category}'):
|
||||
if cogfile.endswith('.py'):
|
||||
loadCog(f'./{cogsDir}/{category}/{cogfile}')
|
||||
|
||||
def unloadCogs(cogClass:str = 'all'):
|
||||
for category in os.listdir(f'./{cogsDir}'):
|
||||
if cogClass == 'all' or cogClass == category:
|
||||
for cogfile in os.listdir(f'./{cogsDir}/{category}'):
|
||||
if cogfile.endswith('.py'):
|
||||
unloadCog(f'./{cogsDir}/{category}/{cogfile}')
|
||||
|
||||
loadCogs('dev')
|
||||
loadCogs('events')
|
||||
loadCogs('botcommands')
|
||||
|
||||
client.run(os.getenv('TEST_3_TOKEN'))
|
189
app/dev_old.py
189
app/dev_old.py
@ -1,189 +0,0 @@
|
||||
import os # OS Locations
|
||||
from dotenv import load_dotenv # Import OS variables from Dotenv file.
|
||||
load_dotenv() # Load Dotenv. Delete this for production
|
||||
import configparser # Config ini parser for Bot config files
|
||||
import json # Json Library to manage json Data files
|
||||
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
|
||||
|
||||
# Locate configuration file
|
||||
configFile = './data/config.ini'
|
||||
|
||||
# Create empty list of current guilds, to be populated Bot load
|
||||
guild_ids = []
|
||||
|
||||
# Config keys
|
||||
configKeys = ['botrole', 'committeerole', 'modchannel', 'prefix']
|
||||
|
||||
# Bot Prefix Function for Regular String Prefix (non-Slash commands)
|
||||
def getPrefix(client,message):
|
||||
conf = configparser.ConfigParser()
|
||||
conf.read(configFile)
|
||||
if message.guild.id in conf.sections():
|
||||
if 'prefix' in conf[message.guild.id]:
|
||||
return conf[message.guild.id]['prefix']
|
||||
pass
|
||||
|
||||
# Define bot client and initialise Slash Command client
|
||||
client = commands.Bot(command_prefix= '¬', intents=discord.Intents.all()) # Regular client set up, but I did not know you could pass a function as a prefix!
|
||||
slash = SlashCommand(client, sync_commands=True) # Enable Slash Command Functionality
|
||||
|
||||
# Starting Bot Initialisation
|
||||
@client.event
|
||||
async def on_guild_join(guild):
|
||||
pass
|
||||
|
||||
# @slash.slash(
|
||||
# name='configure',
|
||||
# description='Configuration command to set up the various parameters for the bot on the server.'
|
||||
# guild_ids=guild_ids,
|
||||
# options = [
|
||||
# create_option(
|
||||
# name='parameter1',
|
||||
# description='Please select the first parameter you would like to configure.',
|
||||
# type=3,
|
||||
# required=True
|
||||
# )
|
||||
# ]
|
||||
# )
|
||||
|
||||
# On Ready
|
||||
@client.event
|
||||
async def on_ready():
|
||||
print('Bot Ready')
|
||||
for guild in client.guilds:
|
||||
guild_ids.append(guild.id)
|
||||
channel = discord.utils.get(guild.text_channels,position=1)
|
||||
print(channel)
|
||||
print(guild_ids)
|
||||
conf = configparser.ConfigParser()
|
||||
conf.read(configFile)
|
||||
undef = []
|
||||
if guild.id in conf.sections(): # Check if there are configs for the guild, and check if configs are complete
|
||||
for i in configKeys:
|
||||
if i not in conf[guild.id]:
|
||||
undef.append(i)
|
||||
if len(undef) == 0: # If none of the key values are undefined, ignore it.
|
||||
channel = discord.utils.get(guild.text_channels,id=conf[guild.id]['modchannel'])
|
||||
output = f'`{client.user.display_name}` has already been configured for the guild `{guild.name}`. \n'
|
||||
output = ''.join([output, f'The `botrole` for the guild `{guild.name}` is {discord.utils.get(guild.roles,id=conf[guild.id]["botrole"]).mention}\n'])
|
||||
output = ''.join([output, f'The `committeerole` for the guild `{guild.name}` is {discord.utils.get(guild.roles,id=conf[guild.id]["committeerole"]).mention}\n'])
|
||||
output = ''.join([output, f'The `modchannel` for the guild `{guild.name}` is {channel.mention}\n'])
|
||||
output = ''.join([output, f'The `prefix` for the guild `{guild.name}` is `{conf[guild.id]["prefix"]}`'])
|
||||
await channel.send(output)
|
||||
break
|
||||
if len(undef) == 0:
|
||||
undef = configKeys.copy()
|
||||
output = f'`{client.user.display_name}` has not been configured for the guild `{guild.name}`. Please define:\n'
|
||||
for u in undef:
|
||||
output = ''.join([output, f'`{u}`\n'])
|
||||
output = ''.join([output, f'using the `/configure` command.'])
|
||||
await channel.send(output)
|
||||
|
||||
|
||||
@slash.slash(
|
||||
name='hello',
|
||||
description='Hello World command',
|
||||
guild_ids=guild_ids,
|
||||
options = [
|
||||
create_option(
|
||||
name='option',
|
||||
description='choose your word',
|
||||
required=True,
|
||||
option_type=3
|
||||
)
|
||||
]
|
||||
)
|
||||
async def _hello(ctx:SlashContext, option:str):
|
||||
await ctx.send(option)
|
||||
|
||||
@slash.slash(
|
||||
name='mypfp',
|
||||
description='Displays profile picture',
|
||||
guild_ids=guild_ids
|
||||
)
|
||||
async def pfp(ctx):
|
||||
embed = discord.Embed(
|
||||
title=f'Avatar of {ctx.author.display_name}',
|
||||
color=discord.Color.teal()
|
||||
).set_image(url=ctx.author.avatar_url)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
class Cogs(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
@commands.command(description='Sends Pong')
|
||||
async def ping(self, ctx):
|
||||
await ctx.send('pong')
|
||||
|
||||
@cog_ext.cog_slash(name='Ping', description='Sends Pong')
|
||||
async def ping(self, ctx):
|
||||
await ctx.send('pong')
|
||||
|
||||
|
||||
@client.command(name='foo')
|
||||
async def foo(ctx):
|
||||
f = await utils.manage_commands.get_all_commands(client.user.id,os.getenv('TEST_3_TOKEN'),guild_id=ctx.guild.id)
|
||||
print(f)
|
||||
|
||||
def setup(bot):
|
||||
client.add_cog(Cogs(client))
|
||||
|
||||
|
||||
###### Configuration Cog
|
||||
# class Configuration(commands.Cog):
|
||||
# def __init__(self, client):
|
||||
# self.client = client
|
||||
|
||||
# @cog_ext.cog_slash(
|
||||
# # base='botrole',
|
||||
# # subcommand_group='configure',
|
||||
# name='configure',
|
||||
# description='Parameter to define the role that is assigned to the dice bots in this guild so they can access in-game text channels.',
|
||||
# # base_description='Command to configure the various guild parameters.',
|
||||
# # subcommand_group_description='These are configuration commands to set up the various guild parameters.',
|
||||
# guild_ids=guild_ids
|
||||
# # options=[
|
||||
# # create_option(
|
||||
# # name='botrole',
|
||||
# # description='The role that the dice bots are assigned in order to access the text channels.'
|
||||
# # type=8,
|
||||
# # required=True
|
||||
# # )
|
||||
# # ]
|
||||
# )
|
||||
# async def _configure(self, ctx:SlashContext, option):
|
||||
# await ctx.send(f'The `botrole` for the guild `{ctx.guild.name}` has been set to `{option.mention}`.')
|
||||
|
||||
# def setup(client):
|
||||
# client.add_cog(Configuration(client))
|
||||
|
||||
client.run(os.getenv('TEST_3_TOKEN'))
|
||||
|
||||
# guilds = [
|
||||
# 'guild1',
|
||||
# 'guild2',
|
||||
# 'guild3'
|
||||
# ]
|
||||
|
||||
# configFile = './config.ini'
|
||||
|
||||
# config = configparser.RawConfigParser()
|
||||
|
||||
# for g in guilds:
|
||||
# config.add_section(g)
|
||||
# config.set(g,'botrole',f'<Bot Role> for {g}')
|
||||
# config.set(g,'committeerole',f'<Committee Role> for {g}')
|
||||
|
||||
# with open(configFile,'w') as c:
|
||||
# config.write(c)
|
||||
|
||||
# parser = configparser.ConfigParser()
|
||||
# parser.read(configFile)
|
||||
# print(parser.sections())
|
||||
# print(parser['guild1']['botrole'])
|
Loading…
Reference in New Issue
Block a user