class DeleteRankCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash( name='deleterank', description= 'Delete your ranking number and the corresponding offer date (if it exists) from the bot', options=[ create_option(name='programme', description='Study programme', option_type=command_option_type.STRING, required=True, choices=programmes_helper.get_programme_choices() + [create_choice(name='All programmes', value='all')]), create_option(name='year', description='Year of application', option_type=command_option_type.INTEGER, required=True, choices=programmes_helper.get_year_choices()) ]) async def deleterank(self, ctx: SlashContext, programme: str, year: int): user = ctx.author if programme == 'all': programme = None async with (await self.bot.get_db_conn()).acquire() as connection: ranks = ranks_service.RanksService(connection) await ranks.delete_rank(str(user.id), programme, year) await ctx.send(user.mention + ' Rank deleted.')
class OffergraphCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash( name='offergraph', description= 'Show a graph of ranking numbers and the dates when they received offers', options=[ create_option(name='programme', description='Study programme', option_type=command_option_type.STRING, required=True, choices=programmes_helper.get_programme_choices()), create_option(name='year', description='Year of application', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices()), create_option(name='approx', description='Show approximation line', option_type=command_option_type.BOOLEAN, required=False), create_option( name='public', description='Show the result of the command to everyone', option_type=command_option_type.BOOLEAN, required=False, ) ]) async def offergraph(self, ctx: SlashContext, programme: str, year: int = None, approx: bool = True, public: bool = False): if year is None: year = constants.current_year if not ctx.guild or 'bot' in ctx.channel.name: public = True # Show "Bot is thinking" message await ctx.defer(hidden=not public) async with (await self.bot.get_db_conn()).acquire() as connection: offers = offers_service.OffersService(connection) try: filename = await offers.generate_graph( programmes_helper.programmes[programme], not approx, year) except ValueError: await ctx.send('This programme was not numerus fixus in ' + str(year), hidden=not public) return image = discord.File(filename) await ctx.send(file=image, hidden=not public) await offers.clean_up_file(filename)
class OffersCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash(name='offers', description='Show highest known ranks with offers', options=[ create_option( name='year', description='Year of application', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices() ) ]) async def offers(self, ctx: SlashContext, year: int = None): if year is None: year = constants.current_year async with (await self.bot.get_db_conn()).acquire() as connection: offers_svc = offers_service.OffersService(connection) offers = await offers_svc.get_highest_ranks_with_offers(year) embed = discord.Embed(title=f"Highest known ranks with offers ({year})", color=0x36bee6) for offer in offers: programme = programmes_helper.programmes[offer[0]] rank = offer[1] date_str = offer_date_util.format_offer_date(offer[2]) is_private = offer[3] is True embed.add_field(name=f'**{programme.icon} {programme.uni_name}\n{programme.display_name.ljust(33, " ")}**', value=f'**{(("≈" + str(offers_service.round_rank(rank))) if is_private else str(rank))}**' f' on {date_str}', inline=True) any_rounded = any(map(lambda x: x[3] is True, offers)) embed.add_field(name='To see a graph of ranking numbers and the dates when they received offers,' ' use `/offergraph`.', value='This data has been provided by server members.' + (' Some ranking numbers (as indicated ' 'by **≈** in front of them) have been rounded to the nearest multiple of 5 ' 'to help protect users\' privacy.' if any_rounded else '') + '\nTo set your ranking number, use `/setrank`. ' 'Then, to set the date you received an offer, use `/setofferdate`.', inline=False) await ctx.send(embed=embed)
class ToggleprivaterankCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash(name='toggleprivaterank', description='Toggle whether your rank is displayed to other people', options=[ create_option( name='programme', description='Study programme', option_type=command_option_type.STRING, required=False, choices=programmes_helper.get_programme_choices() ), create_option( name='year', description='Year of application', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices() ) ]) async def toggleprivaterank(self, ctx: SlashContext, programme: str = None, year: int = None): user = ctx.author user_id = str(user.id) if year is None: year = constants.current_year async with (await self.bot.get_db_conn()).acquire() as connection: ranks = ranks_service.RanksService(connection) if programme is None and await ranks.get_has_only_one_rank(user_id, year): is_private = await ranks.get_is_private(user_id, year) await ranks.set_is_private(user_id, not is_private, year) else: if programme is None: await ctx.send(user.mention + ' Please specify the programme of the rank you wish to ' 'toggle the visibility of.') return is_private = await ranks.get_is_private_programme(user_id, programme, year) if is_private is None: await ctx.send(user.mention + ' You haven\'t set your ranking number for this programme yet.') return await ranks.set_is_private_programme(user_id, not is_private, programme, year) await ctx.send(user.mention + f' Your rank is {"no longer" if is_private else "now"} hidden from `.ranks`')
class OffergraphCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash( name='offergraph', description= 'Show a graph of ranking numbers and the dates when they received offers', options=[ create_option(name='programme_id', description='Study programme', option_type=command_option_type.STRING, required=True, choices=programmes_helper.get_programme_choices()), create_option(name='year', description='Year of application', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices()), create_option(name='step', description='Show only step graph', option_type=command_option_type.BOOLEAN, required=False) ]) async def offergraph(self, ctx: SlashContext, programme_id: str, year: int = None, step: bool = False): if year is None: year = constants.current_year # Show "Bot is thinking" message await ctx.defer() async with (await self.bot.get_db_conn()).acquire() as connection: offers = offers_service.OffersService(connection) await offers.generate_graph( programmes_helper.programmes[programme_id], step, year) image = discord.File(offers_service.filename) await ctx.send(file=image)
class SetofferdateCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash( name='setofferdate', description= 'Set the date you received an offer on Studielink to help people predict when they ' 'might get theirs', options=[ create_option(name='day', description='The day when you received your offer', option_type=command_option_type.INTEGER, required=True), create_option(name='month', description='The month when you received your offer', option_type=command_option_type.INTEGER, required=True, choices=[ create_choice(name='April', value=4), create_choice(name='May', value=5), create_choice(name='June', value=6), create_choice(name='July', value=7), create_choice(name='August', value=8) ]), create_option(name='programme', description='Study programme', option_type=command_option_type.STRING, required=True, choices=programmes_helper.get_programme_choices()), create_option(name='year', description='Year of application', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices()) ]) async def setofferdate(self, ctx: SlashContext, day: int, month: int, programme: str, year: int = None): if year is None: year = constants.current_year user = ctx.author try: offer_date = date(year, month, day) except ValueError: await ctx.send(user.mention + ' Invalid command arguments.') return async with (await self.bot.get_db_conn()).acquire() as connection: ranks = ranks_service.RanksService(connection) try: await ranks.set_offer_date(str(user.id), programme, offer_date, year) except EntryNotFoundError: await ctx.send( user.mention + ' Before setting an offer date, please set your rank first using ' '`/setrank`') return except DateIncorrectError: await ctx.send( user.mention + ' There\'s no need to set the offer date as your rank is within the ' 'programme limit.') return await ctx.send(user.mention + ' Offer date set. Thank you.')
class GetrankCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash(name="getrank", description="Look up all (public) ranking numbers of a user", options=[ create_option( name="user", description="The user you are performing the lookup on", option_type=command_option_type.USER, required=True), create_option( name='programme', description='Study programme', option_type=command_option_type.STRING, required=False, choices=programmes_helper.get_programme_choices()), create_option(name='year', description='Application year', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices()) ]) async def get_rank(self, ctx: SlashContext, user: discord.User, programme: str = None, year: int = None): user_id = str(user.id) async with (await self.bot.get_db_conn()).acquire() as connection: users = user_data_service.UserDataService(connection) res = await users.get_user_ranks(user_id) if res: # Filter by is_private res = filter(lambda x: not x[0], res) # Filter by programme if programme: res = filter(lambda x: x[3] == programme, res) # Filter by year if year: res = filter(lambda x: x[2] == year, res) res = list(res) res.sort(key=lambda x: x[1]) res = "\n".join( map( lambda x: f"Rank {x[1]} in " f"{programmes_helper.programmes[x[3]].uni_name} " f"{programmes_helper.programmes[x[3]].display_name} " f"{x[2]}", res)) if res: await ctx.send( f"User {user} has shared the following public ranking numbers:\n\n" + res) else: await ctx.send( f"User {user} does not have public data that matches your filters" )
class RanksCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash(name='ranks', description='Show all public ranks (not necessarily accepted)', options=[ create_option(name='year', description='Year of application', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices()) ]) async def ranks(self, ctx: SlashContext, year: int = None): if year is None: year = constants.current_year async with (await self.bot.get_db_conn()).acquire() as connection: ranks = ranks_service.RanksService(connection) grouped_ranks = await ranks.get_top_ranks(year) is_bot_channel = not ctx.guild or 'bot' in ctx.channel.name group_truncated = {} if not is_bot_channel: for i in range(len(grouped_ranks)): group_name = grouped_ranks[i][0] truncated_list = grouped_ranks[i][1][:10] group_truncated[group_name] = len(grouped_ranks[i][1]) - 10 grouped_ranks[i] = (group_name, truncated_list) embed_dict = dict() for group in grouped_ranks: programme = programmes_helper.programmes[group[0]] group_name = f'**{programme.icon} {programme.uni_name}\n{programme.display_name.ljust(33, " ")}**' group_list = list( ('`' + (' ' * (3 - len(str(x[1])))) + str(x[1]) + f' {x[0]}`') for x in group[1]) if not is_bot_channel and group_truncated[group[0]] > 0: group_list.append( f'\n**_+ {group_truncated[group[0]]} more..._**') embed_dict[group_name] = group_list embed = discord.Embed(title=f"Ranking numbers ({year})", color=0x36bee6) embed.add_field( name='Note: Not everyone in this list has received an offer.', value= 'To view the highest known ranking numbers with offers, use `/offers`.', inline=False) build_embed_groups(embed, embed_dict) if any(x > 0 for x in group_truncated.values()): embed.add_field( name='**_List is truncated_**', value= 'To view the full list, please use this command in a bot channel, such as ' '<#556533405794172939>\n', inline=False) embed.add_field( name='To set your ranking number, use `/setrank`.', value= '_Please note: This command is purely for fun, the ranking numbers do not' ' represent performance at university_', inline=False) await ctx.send(embed=embed)
class SetrankCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash(name='setrank', description='Set your ranking number', options=[ create_option(name='rank', description='Your ranking number', option_type=command_option_type.INTEGER, required=True), create_option( name='programme', description='Study programme', option_type=command_option_type.STRING, required=True, choices=programmes_helper.get_programme_choices()), create_option(name='year', description='Year of application', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices()) ]) async def setrank(self, ctx: SlashContext, rank: int, programme: str, year: int = None): user = ctx.author user_id = str(user.id) if year is None: year = constants.current_year async with (await self.bot.get_db_conn()).acquire() as connection: ranks = ranks_service.RanksService(connection) users = user_data_service.UserDataService(connection) curr_rank_details = await ranks.get_rank_details_for_programme_and_user( programme, year, user_id) if curr_rank_details: curr_rank, curr_is_private = curr_rank_details if rank == curr_rank: if not curr_is_private: await ctx.send( user.mention + ' You have already set your ranking number. It can be seen via `/ranks`. ' 'If you\'re trying to set an offer date, use `/setofferdate`.' ) else: await ranks.set_is_private_programme( user_id, False, programme, year) await ctx.send( user.mention + ' You have already set your ranking number, but it was private (you\'ve likely ' 'set it by replying to a direct message by the bot).\nIt has now been made ' 'visible and you can see it via `/ranks`. ' 'If you want to make it private again, you can use `/toggleprivaterank`.\n' 'If you\'re trying to set an offer date, you can use `/setofferdate`.' ) else: if not curr_is_private: await ctx.send( user.mention + f' You have already set a different ranking number (**{curr_rank}**). ' 'To change it, first clear the old one using `/clearrank` and then try setting ' 'the new one again.') else: await ctx.send( user.mention + ' You have already set a different ranking number, but it is private (you\'ve ' 'likely set it by replying to a direct message by the bot). ' 'To change it, first clear the old one using `/clearrank` and then try setting ' 'the new one again.') return tr = connection.transaction() await tr.start() try: try: await ranks.add_rank(rank, programme, year, user_id, source='command') except ValueError: await ctx.send(user.mention + ' Invalid command arguments.') await tr.rollback() return except EntryAlreadyExistsError: await ctx.send( user.mention + ' You have already set your ranking number. To set a different one, ' 'clear it using `/clearrank` and try setting it again.' ) await tr.rollback() return await users.add_user(user_id, user.name) except: await tr.rollback() raise await tr.commit() await ctx.send( user.mention + ' Your ranking number was successfully added. It is now set as public and can be ' 'seen via `/ranks`. If you want to make it private, you can use ' '`/toggleprivaterank.`\n' 'If you have received an offer, please use `/setofferdate` to set it.' )
class GetrankCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash(name="getrank", description="Get the ranking number of the specified user", options=[ create_option(name="user", description="The user", option_type=command_option_type.STRING, required=True), create_option( name='programme', description='Study programme', option_type=command_option_type.STRING, required=False, choices=programmes_helper.get_programme_choices()), create_option(name='year', description='Application Year', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices()) ]) async def get_rank(self, ctx: SlashContext, user: str, programme: str = None, year: int = constants.current_year): async with (await self.bot.get_db_conn()).acquire() as connection: user_id = re.sub(r"[<@!>]", "", user) if not user_id.isdigit(): await ctx.send(ctx.author.mention + "Invalid user") return users = user_data_service.UserDataService(connection) res = await users.get_user_rank(user_id) if res: # Filter by programme if programme: res = filter( lambda x: True if x[3] == programme else False, res) # Filter by year res = filter(lambda x: True if x[2] == year else False, res) # Filter by is_private res = list(filter(lambda x: True if not x[0] else False, res)) res.sort(key=lambda x: x[1]) res = "\n".join( map(lambda x: f"Rank: {x[1]} in {x[3]} {x[2]}", res)) if len(res) == 0: await ctx.send( ctx.author.mention + f" User {user} does not have recorded data that matches your filters" ) else: await ctx.send(ctx.author.mention + f"User {user}: \n" + res) else: await ctx.send( ctx.author.mention + f"User {user} is either invalid or has not posted his ranking number." ) return None
class SetrankCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash(name='setrank', description='Set your ranking number', options=[ create_option(name='rank', description='Your ranking number', option_type=command_option_type.INTEGER, required=True), create_option( name='programme', description='Study programme', option_type=command_option_type.STRING, required=True, choices=programmes_helper.get_programme_choices()), create_option(name='year', description='Year of application', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices()) ]) async def setrank(self, ctx: SlashContext, rank: int, programme: str, year: int = None): user = ctx.author user_id = str(user.id) if year is None: year = constants.current_year async with (await self.bot.get_db_conn()).acquire() as connection: ranks = ranks_service.RanksService(connection) users = user_data_service.UserDataService(connection) tr = connection.transaction() await tr.start() try: try: await ranks.add_rank(rank, programme, year, user_id, source='command') except ValueError: await ctx.send(user.mention + ' Invalid command arguments.') await tr.rollback() return except EntryAlreadyExistsError: await ctx.send( user.mention + ' You have already set your ranking number. To set a different one, ' 'clear it using `/clearrank` and try setting it again.' ) await tr.rollback() return await users.add_user(user_id, user.name) except: await tr.rollback() raise await tr.commit() await ctx.send(user.mention + ' Rank set.')
class AddmanualdateCommand(commands.Cog): def __init__(self, bot): self.bot = bot @slash(name='addmanualdate', description='Manually add the date of an offer which is not yours ' '(for example, one that you found online)', options=[ create_option( name='programme', description='Study programme', option_type=command_option_type.STRING, required=True, choices=programmes_helper.get_programme_choices()), create_option(name='rank', description='Ranking number', option_type=command_option_type.INTEGER, required=True), create_option(name='day', description='The day of the offer', option_type=command_option_type.INTEGER, required=True), create_option(name='month', description='The month of the offer', option_type=command_option_type.INTEGER, required=True, choices=[ create_choice(name='April', value=4), create_choice(name='May', value=5), create_choice(name='June', value=6), create_choice(name='July', value=7), create_choice(name='August', value=8) ]), create_option( name='source', description='Source of the data (e.g. reddit, whatsapp...)', option_type=command_option_type.STRING, required=False), create_option(name='year', description='Year of application', option_type=command_option_type.INTEGER, required=False, choices=programmes_helper.get_year_choices()) ]) async def addmanualdate(self, ctx: SlashContext, programme: str, rank: int, day: int, month: int, source: str = None, year: int = None): user = ctx.author if not ctx.guild: await ctx.send( user.mention + ' You don\'t have permission to execute this command via DM') return if not source: source = 'manual' if year is None: year = constants.current_year async with (await self.bot.get_db_conn()).acquire() as connection: ranks = ranks_service.RanksService(connection) tr = connection.transaction() await tr.start() try: offer_date = date(year, month, day) await ranks.add_rank(rank, programme, year, offer_date=offer_date, source=source) if rank <= programmes_helper.programmes[programme].places[year]: raise DateIncorrectError await tr.commit() await ctx.send(user.mention + ' Rank and offer date added.') except DateIncorrectError: await tr.rollback() await ctx.send( user.mention + ' There\'s no need to set this offer date as this rank is ' 'within the programme limit.') except ValueError: await tr.rollback() await ctx.send(user.mention + ' Invalid command arguments.') except: await tr.rollback() raise