v2.1.1 of the bot, which is currently in use on the Geas server.
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					db/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ---> Python
 | 
					# ---> Python
 | 
				
			||||||
# Byte-compiled / optimized / DLL files
 | 
					# Byte-compiled / optimized / DLL files
 | 
				
			||||||
__pycache__/
 | 
					__pycache__/
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										69
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										69
									
								
								README.md
									
									
									
									
									
								
							@@ -1,3 +1,68 @@
 | 
				
			|||||||
# geas-bot
 | 
					# Geas Server Bot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Bot for managing the Discord server for Geas, the Edinburgh table-top role-playing society.
 | 
					This is a bot I wrote to manage the Discord server for Geas, the Edinburgh University Table-Top Role-Playing Society, during our move to an on-line format.
 | 
				
			||||||
 | 
					The bot is designed to create and manage channels and roles for gaming groups in order to replicate our in-person pitch events on a Discord space as far as possible.
 | 
				
			||||||
 | 
					The bot is written in Python, and was the first Python coding project I wrote, so it has a special place in my heart.
 | 
				
			||||||
 | 
					The first version I am committing to the repository is version 2.1, and I previously handled the version control manually, so migrating old versions to Git would be a pain.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Bot Setup
 | 
				
			||||||
 | 
					The Bot is dockerised and uses docker-compose for deployment, so it is fairly straightforward to set up.
 | 
				
			||||||
 | 
					Clone the repository, install Docker and Docker Compose, navigate to the root directory (that contains the `docker-compose.yml` file), and use `docker-compose up -d` to set up and run the bot.
 | 
				
			||||||
 | 
					The bot uses two containers that are networked internally:
 | 
				
			||||||
 | 
					> 1. A python app that runs the bot, and
 | 
				
			||||||
 | 
					> 2. A MongoDB database that stores the bot's data for persistence.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The database is not exposed externally to the network, and can only be accessed by the Bot in the network of containers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The bot authenticates using an API key, which I have kept private in a `.env` file that I have not uploaded to the repository.
 | 
				
			||||||
 | 
					In order to set up your own instance of the bot, you will need to create two copies of the `.env` file, one in the root directory and one in the `app` folder, and enter the respective values for the API keys for the Geas Server Bot and the Test Bot.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You will also need this database to set up a username and password for the MongoDB database.
 | 
				
			||||||
 | 
					The specific username and password don't matter as the bot refers back to the environment variable when authenticating.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The following is the template for the `.env` file, with the variable names as are referenced in the bot's code:
 | 
				
			||||||
 | 
					`.env` file:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					BOT_TOKEN=
 | 
				
			||||||
 | 
					TEST_TOKEN=
 | 
				
			||||||
 | 
					MONGO_INITDB_ROOT_USERNAME=
 | 
				
			||||||
 | 
					MONGO_INITDB_ROOT_PASSWORD=
 | 
				
			||||||
 | 
					BOT_VERSION=2.1.1
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					The only thing that remains is for the correct API keys to be entered in the environment variables in the `.env` file, and for a copy of this file to be placed in the root and the `app` directories.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**N.B.**: When the bot is first run, it is configured to log in as the Test Bot, and not the main Geas Server Bot, as a safety measure.
 | 
				
			||||||
 | 
					To change this, navigate to the last line of the file `bot.py` and change the line:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					client.run(os.getenv('TEST_TOKEN'))
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					to
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					client.run(os.getenv('BOT_TOKEN'))
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					in order for to authenticate as the correct bot.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Bot Structure
 | 
				
			||||||
 | 
					The bot is divided into the following files:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					app folder
 | 
				
			||||||
 | 
					|   bot.py -- bot core functionality and code entrypoint
 | 
				
			||||||
 | 
					|   Dockerfile -- Docker instructions on building the bot
 | 
				
			||||||
 | 
					|   requirements.txt -- Dependencies to be installed
 | 
				
			||||||
 | 
					----cogs -- Individual modules for specific features
 | 
				
			||||||
 | 
					       GameManagement.py -- adding or kicking players
 | 
				
			||||||
 | 
					       HelpNotifier.py -- notifications for Help channel
 | 
				
			||||||
 | 
					       MembershipRestriction.py -- restrictions unverified users
 | 
				
			||||||
 | 
					       MembershipVerification.py -- membership verification system
 | 
				
			||||||
 | 
					       PitchMenu.py -- automation for generating menus for game pitches
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Many of the specific features, such as the bot's prefix, the roles it recognises as Committee, the channels it recognises as the Help or Membership Verification channels, are all hard-coded into the Bot.
 | 
				
			||||||
 | 
					This is because the bot was only ever supposed to be used on one server, so did not need the flexibility of adapting to multiple channels.
 | 
				
			||||||
 | 
					In the future, if I ever tinker with this in the future, I might try and add flexibility in the channels and roles it defines for its various functions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I might also, in future incarnations, not use a database.
 | 
				
			||||||
 | 
					It was fun to learn how to use a database, but it is overkill.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Bot Commands
 | 
				
			||||||
 | 
					A full list of bot commands can be retrieved using the `-help` command in the bot, and this might be an easier way of retrieving the commands than having a separate copy in the documentation.
 | 
				
			||||||
							
								
								
									
										6
									
								
								app/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					FROM python:3.8.6-buster
 | 
				
			||||||
 | 
					COPY . /usr/src/app
 | 
				
			||||||
 | 
					WORKDIR /usr/src/app
 | 
				
			||||||
 | 
					RUN pip install --upgrade pip
 | 
				
			||||||
 | 
					RUN pip install -r requirements.txt
 | 
				
			||||||
 | 
					CMD python3 -u ./bot.py
 | 
				
			||||||
							
								
								
									
										466
									
								
								app/bot.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										466
									
								
								app/bot.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,466 @@
 | 
				
			|||||||
 | 
					# Import Dependencies
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import pymongo
 | 
				
			||||||
 | 
					import discord
 | 
				
			||||||
 | 
					from discord.ext import commands, tasks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Set Intents
 | 
				
			||||||
 | 
					intents = discord.Intents.all()
 | 
				
			||||||
 | 
					intents.typing = True
 | 
				
			||||||
 | 
					intents.presences = True
 | 
				
			||||||
 | 
					intents.members = True
 | 
				
			||||||
 | 
					caches = discord.MemberCacheFlags.all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Set Prefix
 | 
				
			||||||
 | 
					p = '¬'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Define Global State Dictionary
 | 
				
			||||||
 | 
					state = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create Clients
 | 
				
			||||||
 | 
					dbClient = pymongo.MongoClient(host='geasbot-db', username=os.environ['MONGO_INITDB_ROOT_USERNAME'], password=os.environ['MONGO_INITDB_ROOT_PASSWORD'], authsource='admin', serverSelectionTimeoutMS=1000)
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Define Game Times Dictionary
 | 
				
			||||||
 | 
					gameTimes = {
 | 
				
			||||||
 | 
					    'wed': 'WED',
 | 
				
			||||||
 | 
					    'sunaft': 'SUN AFT',
 | 
				
			||||||
 | 
					    'suneve': 'SUN EVE',
 | 
				
			||||||
 | 
					    'oneshot': 'ONE SHOT'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Reference Time Codes
 | 
				
			||||||
 | 
					def gameTime(arg):
 | 
				
			||||||
 | 
					    return gameTimes.get(arg, 'OTHER')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# List Time Codes
 | 
				
			||||||
 | 
					def timeSlotList():
 | 
				
			||||||
 | 
					    l = []
 | 
				
			||||||
 | 
					    for t in gameTimes:
 | 
				
			||||||
 | 
					        l.append(gameTimes[t])
 | 
				
			||||||
 | 
					    l.append('OTHER')
 | 
				
			||||||
 | 
					    return l
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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)
 | 
				
			||||||
 | 
					    db = dbClient[dbName]
 | 
				
			||||||
 | 
					    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.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Easter egg for Alan
 | 
				
			||||||
 | 
					@client.command()
 | 
				
			||||||
 | 
					async def alan(ctx):
 | 
				
			||||||
 | 
					    if ctx.author.id != 355857207205691392:
 | 
				
			||||||
 | 
					        alan = await ctx.guild.fetch_member(355857207205691392)
 | 
				
			||||||
 | 
					        raise commands.CommandError(f'Error: This is a powerful, super secret master command that only {alan.mention} can use. Do **NOT** invoke this if you are not Alan. ~~It is a command that can do great evil in the wrong hands.~~')
 | 
				
			||||||
 | 
					    await ctx.send(f'Hi {alan.mention}. In order to execute this script, you must click the following link:\n<http://vsnt.uk/alancommand>\nUse it wisely.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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'))
 | 
				
			||||||
							
								
								
									
										177
									
								
								app/cogs/GameManagement.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								app/cogs/GameManagement.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,177 @@
 | 
				
			|||||||
 | 
					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))
 | 
				
			||||||
							
								
								
									
										48
									
								
								app/cogs/HelpNotifier.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								app/cogs/HelpNotifier.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					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))
 | 
				
			||||||
							
								
								
									
										112
									
								
								app/cogs/MembershipRestriction.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								app/cogs/MembershipRestriction.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
				
			|||||||
 | 
					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))
 | 
				
			||||||
							
								
								
									
										142
									
								
								app/cogs/MembershipVerification.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								app/cogs/MembershipVerification.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
				
			|||||||
 | 
					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))
 | 
				
			||||||
							
								
								
									
										190
									
								
								app/cogs/PitchMenu.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								app/cogs/PitchMenu.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
				
			|||||||
 | 
					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))
 | 
				
			||||||
							
								
								
									
										5
									
								
								app/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/requirements.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					discord
 | 
				
			||||||
 | 
					python-dotenv
 | 
				
			||||||
 | 
					wheel
 | 
				
			||||||
 | 
					pymongo
 | 
				
			||||||
 | 
					datetime
 | 
				
			||||||
							
								
								
									
										29
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					version: '3.4'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					services:
 | 
				
			||||||
 | 
					  geasbot-app:
 | 
				
			||||||
 | 
					    build: ./app
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./app:/usr/src/app
 | 
				
			||||||
 | 
					    restart: always
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - geasbot-db
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      - BOT_TOKEN=${BOT_TOKEN}
 | 
				
			||||||
 | 
					      - TEST_TOKEN=${TEST_TOKEN}
 | 
				
			||||||
 | 
					      - MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME}
 | 
				
			||||||
 | 
					      - MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD}
 | 
				
			||||||
 | 
					      - BOT_VERSION=${BOT_VERSION}
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  geasbot-db:
 | 
				
			||||||
 | 
					    image: mongo:latest
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./db:/data/db
 | 
				
			||||||
 | 
					    expose:
 | 
				
			||||||
 | 
					      - "27017"
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      - BOT_TOKEN=${BOT_TOKEN}
 | 
				
			||||||
 | 
					      - TEST_TOKEN=${TEST_TOKEN}
 | 
				
			||||||
 | 
					      - MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME}
 | 
				
			||||||
 | 
					      - MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD}
 | 
				
			||||||
 | 
					      - BOT_VERSION=${BOT_VERSION}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user