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)
Пример #3
0
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)
Пример #4
0
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`')
Пример #5
0
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.')
Пример #7
0
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"
            )
Пример #8
0
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)
Пример #9
0
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.'
        )
Пример #10
0
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
Пример #11
0
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