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))