Exemplo n.º 1
0
    async def me(self, ctx, arg: str = None):
        settings = cf_common.user_db.get_reminder_settings(ctx.guild.id)
        if settings is None:
            await ctx.send(embed=discord_common.embed_alert(
                'To use this command, reminder settings must be set by an admin'
            ))
            return
        _, role_id, _ = settings
        role = ctx.guild.get_role(int(role_id))
        if role is None:
            await ctx.send(embed=discord_common.embed_alert(
                'The role set for reminders is no longer available'))
            return

        if arg is None:
            if role in ctx.author.roles:
                await ctx.send(embed=discord_common.embed_neutral(
                    'You are already subscribed to contest reminders'))
                return
            await ctx.author.add_roles(
                role, reason='User subscribed to contest reminders')
            await ctx.send(embed=discord_common.embed_success(
                'Successfully subscribed to contest reminders'))
        elif arg == 'not':
            if role not in ctx.author.roles:
                await ctx.send(embed=discord_common.embed_neutral(
                    'You are not subscribed to contest reminders'))
                return
            await ctx.author.remove_roles(
                role, reason='User unsubscribed from contest reminders')
            await ctx.send(embed=discord_common.embed_success(
                'Successfully unsubscribed from contest reminders'))
Exemplo n.º 2
0
 async def settings(self, ctx):
     """Shows the role, channel and before time settings."""
     settings = cf_common.user_db.get_reminder_settings(ctx.guild.id)
     if settings is None:
         await ctx.send(
             embed=discord_common.embed_neutral('Reminder not set'))
         return
     channel_id, role_id, before = settings
     channel_id, role_id, before = int(channel_id), int(
         role_id), json.loads(before)
     channel, role = ctx.guild.get_channel(channel_id), ctx.guild.get_role(
         role_id)
     if channel is None:
         await ctx.send(embed=discord_common.embed_alert(
             'The channel set for reminders is no longer available'))
         return
     if role is None:
         await ctx.send(embed=discord_common.embed_alert(
             'The role set for reminders is no longer available'))
         return
     before_str = ', '.join(str(before_mins) for before_mins in before)
     embed = discord_common.embed_success('Current reminder settings')
     embed.add_field(name='Channel', value=channel.mention)
     embed.add_field(name='Role', value=role.mention)
     embed.add_field(name='Before',
                     value=f'At {before_str} mins before contest')
     await ctx.send(embed=embed)
Exemplo n.º 3
0
    async def _show_ranklist(self, channel, contest_id: int, handles: [str], ranklist, vc: bool = False, delete_after: float = None):
        contest = cf_common.cache2.contest_cache.get_contest(contest_id)
        if ranklist is None:
            raise ContestCogError('No ranklist to show')

        handle_standings = []
        for handle in handles:
            try:
                standing = ranklist.get_standing_row(handle)
            except rl.HandleNotPresentError:
                continue

            # Database has correct handle ignoring case, update to it
            # TODO: It will throw an exception if this row corresponds to a team. At present ranklist doesnt show teams.
            # It should be fixed in https://github.com/cheran-senthil/TLE/issues/72
            handle = standing.party.members[0].handle
            if vc and standing.party.participantType != 'VIRTUAL':
                continue
            handle_standings.append((handle, standing))

        if not handle_standings:
            error = f'None of the handles are present in the ranklist of `{contest.name}`'
            if vc:
                await channel.send(embed=discord_common.embed_alert(error), delete_after=delete_after)
                return
            raise ContestCogError(error)

        handle_standings.sort(key=lambda data: data[1].rank)
        deltas = None
        if ranklist.is_rated:
            deltas = [ranklist.get_delta(handle) for handle, standing in handle_standings]

        problem_indices = [problem.index for problem in ranklist.problems]
        pages = self._make_standings_pages(contest, problem_indices, handle_standings, deltas)
        paginator.paginate(self.bot, channel, pages, wait_time=_STANDINGS_PAGINATE_WAIT_TIME, delete_after=delete_after)
Exemplo n.º 4
0
Arquivo: graphs.py Projeto: cfalas/TLE
 async def cog_command_error(self, ctx, error):
     if isinstance(error, GraphCogError):
         await ctx.send(embed=discord_common.embed_alert(error))
         error.handled = True
         return
     await cf_common.cf_handle_error_handler(ctx, error)
     await cf_common.run_handle_coro_error_handler(ctx, error)
Exemplo n.º 5
0
 async def remove(self, ctx, original_message_id: int):
     """Remove a particular message from the starboard database."""
     rc = cf_common.user_db.remove_starboard_message(original_msg_id=original_message_id)
     if rc:
         await ctx.send(embed=discord_common.embed_success('Successfully removed'))
     else:
         await ctx.send(embed=discord_common.embed_alert('Not found in database'))
Exemplo n.º 6
0
 async def cog_command_error(self, ctx, error):
     if isinstance(
             error,
         (ContestCogError, rl.RanklistError, cache_system2.CacheError)):
         await ctx.send(embed=discord_common.embed_alert(error))
         error.handled = True
         return
     await cf_common.resolve_handle_error_handler(ctx, error)
Exemplo n.º 7
0
    async def ratedvc(self, ctx, contest_id: int, *members: discord.Member):
        ratedvc_channel_id = cf_common.user_db.get_rated_vc_channel(
            ctx.guild.id)
        if not ratedvc_channel_id or ctx.channel.id != ratedvc_channel_id:
            raise ContestCogError(
                'You must use this command in ratedvc channel.')
        if not members:
            raise ContestCogError('Missing members')
        contest = cf_common.cache2.contest_cache.get_contest(contest_id)
        try:
            (await
             cf.contest.ratingChanges(contest_id=contest_id
                                      ))[_MIN_RATED_CONTESTANTS_FOR_RATED_VC -
                                         1]
        except (cf.RatingChangesUnavailableError, IndexError):
            error = (
                f'`{contest.name}` was not rated for at least {_MIN_RATED_CONTESTANTS_FOR_RATED_VC} contestants'
                ' or the ratings changes are not published yet.')
            raise ContestCogError(error)

        ongoing_vc_member_ids = _get_ongoing_vc_participants()
        this_vc_member_ids = {str(member.id) for member in members}
        intersection = this_vc_member_ids & ongoing_vc_member_ids
        if intersection:
            busy_members = ", ".join([
                ctx.guild.get_member(int(member_id)).mention
                for member_id in intersection
            ])
            error = f'{busy_members} are registered in ongoing ratedvcs.'
            raise ContestCogError(error)

        handles = cf_common.members_to_handles(members, ctx.guild.id)
        visited_contests = await cf_common.get_visited_contests(handles)
        if contest_id in visited_contests:
            raise ContestCogError(
                f'Some of the handles: {", ".join(handles)} have submissions in the contest'
            )
        start_time = time.time()
        finish_time = start_time + contest.durationSeconds + _RATED_VC_EXTRA_TIME
        cf_common.user_db.create_rated_vc(contest_id, start_time, finish_time,
                                          ctx.guild.id,
                                          [member.id for member in members])
        title = f'Starting {contest.name} for:'
        msg = "\n".join(
            f'[{discord.utils.escape_markdown(handle)}]({cf.PROFILE_BASE_URL}{handle})'
            for handle in handles)
        embed = discord_common.cf_color_embed(title=title,
                                              description=msg,
                                              url=contest.url)
        await ctx.send(embed=embed)
        embed = discord_common.embed_alert(
            f'You have {int(finish_time - start_time) // 60} minutes to complete the vc!'
        )
        embed.set_footer(text='GL & HF')
        await ctx.send(embed=embed)
Exemplo n.º 8
0
    async def withdraw(self, ctx):
        active = cf_common.user_db.check_duel_withdraw(ctx.author.id)
        if not active:
            raise DuelCogError(
                f'{ctx.author.mention}, you are not challenging anyone.')

        duelid, challengee = active
        challengee = ctx.guild.get_member(challengee)
        cf_common.user_db.cancel_duel(duelid, Duel.WITHDRAWN)
        message = f'{ctx.author.mention} withdrew a challenge to `{challengee.mention}`.'
        embed = discord_common.embed_alert(message)
        await ctx.send(embed=embed)
Exemplo n.º 9
0
    async def decline(self, ctx):
        active = cf_common.user_db.check_duel_decline(ctx.author.id)
        if not active:
            raise DuelCogError(
                f'{ctx.author.mention}, you are not being challenged!')

        duelid, challenger = active
        challenger = ctx.guild.get_member(challenger)
        cf_common.user_db.cancel_duel(duelid, Duel.DECLINED)
        message = f'`{ctx.author.mention}` declined a challenge by {challenger.mention}.'
        embed = discord_common.embed_alert(message)
        await ctx.send(embed=embed)
Exemplo n.º 10
0
 async def here(self, ctx, role: discord.Role, *before: int):
     """Sets reminder channel to current channel, role to the given role, and reminder
     times to the given values in minutes."""
     if not role.mentionable:
         await ctx.send(embed=discord_common.embed_alert(
             'The role for reminders must be mentionable'))
         return
     if not before or any(before_mins <= 0 for before_mins in before):
         return
     cf_common.user_db.set_reminder_settings(ctx.guild.id, ctx.channel.id,
                                             role.id, json.dumps(before))
     await ctx.send(embed=discord_common.embed_success(
         'Reminder settings saved successfully'))
     self._reschedule_tasks(ctx.guild.id)
Exemplo n.º 11
0
    async def _watch_rated_vc(self, vc_id: int):
        vc = cf_common.user_db.get_rated_vc(vc_id)
        channel_id = cf_common.user_db.get_rated_vc_channel(vc.guild_id)
        if channel_id is None:
            raise ContestCogError('No Rated VC channel')
        channel = self.bot.get_channel(int(channel_id))
        member_ids = cf_common.user_db.get_rated_vc_user_ids(vc_id)
        handles = [cf_common.user_db.get_handle(member_id, channel.guild.id) for member_id in member_ids]
        handle_to_member_id = {handle : member_id for handle, member_id in zip(handles, member_ids)}
        now = time.time()
        ranklist = await cf_common.cache2.ranklist_cache.generate_vc_ranklist(vc.contest_id, handle_to_member_id)

        async def has_running_subs(handle):
            return [sub for sub in await cf.user.status(handle=handle)
                    if sub.verdict == 'TESTING' and
                       sub.problem.contestId == vc.contest_id and
                       sub.relativeTimeSeconds <= vc.finish_time - vc.start_time]

        running_subs_flag = any([await has_running_subs(handle) for handle in handles])
        if running_subs_flag:
            msg = 'Some submissions are still being judged'
            await channel.send(embed=discord_common.embed_alert(msg), delete_after=_WATCHING_RATED_VC_WAIT_TIME)
        if now < vc.finish_time or running_subs_flag:
            # Display current standings
            await channel.send(embed=self._make_contest_embed_for_vc_ranklist(ranklist, vc.start_time, vc.finish_time), delete_after=_WATCHING_RATED_VC_WAIT_TIME)
            await self._show_ranklist(channel, vc.contest_id, handles, ranklist=ranklist, vc=True, delete_after=_WATCHING_RATED_VC_WAIT_TIME)
            return
        rating_change_by_handle = {}
        RatingChange = namedtuple('RatingChange', 'handle oldRating newRating')
        for handle, member_id in zip(handles, member_ids):
            delta = ranklist.delta_by_handle.get(handle)
            if delta is None:  # The user did not participate.
                cf_common.user_db.remove_last_ratedvc_participation(member_id)
                continue
            old_rating = cf_common.user_db.get_vc_rating(member_id)
            new_rating = old_rating + delta
            rating_change_by_handle[handle] = RatingChange(handle=handle, oldRating=old_rating, newRating=new_rating)
            cf_common.user_db.update_vc_rating(vc_id, member_id, new_rating)
        cf_common.user_db.finish_rated_vc(vc_id)
        await channel.send(embed=self._make_vc_rating_changes_embed(channel.guild, vc.contest_id, rating_change_by_handle))
        await self._show_ranklist(channel, vc.contest_id, handles, ranklist=ranklist, vc=True)
Exemplo n.º 12
0
    async def challenge(self, ctx, opponent: discord.Member, *args):
        """Challenge another server member to a duel. Problem difficulty will be the lesser of duelist ratings minus 400. You can alternatively specify a different rating. 
        The duel will be unrated if specified rating is above the default value or tags are used to choose a problem. The challenge expires if ignored for 5 minutes."""
        challenger_id = ctx.author.id
        challengee_id = opponent.id

        await cf_common.resolve_handles(
            ctx, self.converter, ('!' + str(ctx.author), '!' + str(opponent)))
        userids = [challenger_id, challengee_id]
        handles = [
            cf_common.user_db.get_handle(userid, ctx.guild.id)
            for userid in userids
        ]
        submissions = [
            await cf.user.status(handle=handle) for handle in handles
        ]

        if not cf_common.user_db.is_duelist(challenger_id):
            raise DuelCogError(
                f'{ctx.author.mention}, you are not a registered duelist!')
        if not cf_common.user_db.is_duelist(challengee_id):
            raise DuelCogError(
                f'{opponent.mention} is not a registered duelist!')
        if challenger_id == challengee_id:
            raise DuelCogError(
                f'{ctx.author.mention}, you cannot challenge yourself!')
        if cf_common.user_db.check_duel_challenge(challenger_id):
            raise DuelCogError(
                f'{ctx.author.mention}, you are currently in a duel!')
        if cf_common.user_db.check_duel_challenge(challengee_id):
            raise DuelCogError(f'{opponent.mention} is currently in a duel!')

        tags = cf_common.parse_tags(args, prefix='+')
        bantags = cf_common.parse_tags(args, prefix='~')
        rating = cf_common.parse_rating(args)
        users = [cf_common.user_db.fetch_cf_user(handle) for handle in handles]
        lowest_rating = min(user.rating or 0 for user in users)
        suggested_rating = max(
            round(lowest_rating, -2) + _DUEL_RATING_DELTA, 500)
        rating = round(rating, -2) if rating else suggested_rating
        unofficial = rating > suggested_rating or tags or bantags
        dtype = DuelType.UNOFFICIAL if unofficial else DuelType.OFFICIAL

        solved = {
            sub.problem.name
            for subs in submissions for sub in subs
            if sub.verdict != 'COMPILATION_ERROR'
        }
        seen = {
            name
            for userid in userids
            for name, in cf_common.user_db.get_duel_problem_names(userid)
        }

        def get_problems(rating):
            return [
                prob for prob in cf_common.cache2.problem_cache.problems
                if prob.rating == rating and prob.name not in solved
                and prob.name not in seen and not any(
                    cf_common.is_contest_writer(prob.contestId, handle)
                    for handle in handles)
                and not cf_common.is_nonstandard_problem(prob) and prob.
                matches_all_tags(tags) and not prob.matches_any_tag(bantags)
            ]

        for problems in map(get_problems, range(rating, 400, -100)):
            if problems:
                break

        rstr = f'{rating} rated ' if rating else ''
        if not problems:
            raise DuelCogError(
                f'No unsolved {rstr}problems left for {ctx.author.mention} vs {opponent.mention}.'
            )

        problems.sort(key=lambda problem: cf_common.cache2.contest_cache.
                      get_contest(problem.contestId).startTimeSeconds)

        choice = max(random.randrange(len(problems)) for _ in range(2))
        problem = problems[choice]

        issue_time = datetime.datetime.now().timestamp()
        duelid = cf_common.user_db.create_duel(challenger_id, challengee_id,
                                               issue_time, problem, dtype)

        ostr = 'an **unofficial**' if unofficial else 'a'
        await ctx.send(
            f'{ctx.author.mention} is challenging {opponent.mention} to {ostr} {rstr}duel!'
        )
        await asyncio.sleep(_DUEL_EXPIRY_TIME)
        if cf_common.user_db.cancel_duel(duelid, Duel.EXPIRED):
            message = f'{ctx.author.mention}, your request to duel {opponent.mention} has expired!'
            embed = discord_common.embed_alert(message)
            await ctx.send(embed=embed)
Exemplo n.º 13
0
 async def cog_command_error(self, ctx, error):
     if isinstance(error, StarboardCogError):
         await ctx.send(embed=discord_common.embed_alert(error))
         error.handled = True
Exemplo n.º 14
0
async def resolve_handle_error_handler(ctx, error):
    if isinstance(error, ResolveHandleError):
        await ctx.send(embed=discord_common.embed_alert(error))
        error.handled = True
Exemplo n.º 15
0
async def run_handle_coro_error_handler(ctx, error):
    if isinstance(error, RunHandleCoroFailedError):
        await ctx.send(embed=discord_common.embed_alert(error))
        error.handled = True
Exemplo n.º 16
0
async def cf_handle_error_handler(ctx, error):
    if isinstance(error, CodeforcesHandleError):
        await ctx.send(embed=discord_common.embed_alert(error))
        error.handled = True
Exemplo n.º 17
0
    async def ranklist(self, ctx, contest_id: int, *handles: str):
        """Shows ranklist for the contest with given contest id. If handles contains
        '+server', all server members are included. No handles defaults to '+server'.
        """

        contest = cf_common.cache2.contest_cache.get_contest(contest_id)
        wait_msg = None
        try:
            ranklist = cf_common.cache2.ranklist_cache.get_ranklist(contest)
            deltas_status = 'Predicted'
        except cache_system2.RanklistNotMonitored:
            if contest.phase == 'BEFORE':
                raise ContestCogError(
                    f'Contest `{contest.id} | {contest.name}` has not started')
            wait_msg = await ctx.send('Please wait...')
            ranklist = await cf_common.cache2.ranklist_cache.generate_ranklist(
                contest.id, fetch_changes=True)
            deltas_status = 'Final'

        handles = set(handles)
        if not handles:
            handles.add('+server')
        if '+server' in handles:
            handles.remove('+server')
            guild_handles = [
                handle for discord_id, handle in
                cf_common.user_db.get_handles_for_guild(ctx.guild.id)
            ]
            handles.update(guild_handles)
        handles = await cf_common.resolve_handles(ctx,
                                                  self.member_converter,
                                                  handles,
                                                  maxcnt=100)

        handle_standings = []
        for handle in handles:
            try:
                standing = ranklist.get_standing_row(handle)
            except rl.HandleNotPresentError:
                continue
            handle_standings.append((handle, standing))

        if not handle_standings:
            msg = f'None of the handles are present in the ranklist of `{contest.name}`'
            await ctx.send(embed=discord_common.embed_alert(msg))
            return

        handle_standings.sort(key=lambda data: data[1].rank)
        deltas = None
        if ranklist.is_rated:
            deltas = [
                ranklist.get_delta(handle)
                for handle, standing in handle_standings
            ]

        problem_indices = [problem.index for problem in ranklist.problems]
        pages = self._make_standings_pages(contest, problem_indices,
                                           handle_standings, deltas)

        embed = discord_common.cf_color_embed(title=contest.name,
                                              url=contest.url)
        phase = contest.phase.capitalize().replace('_', ' ')
        embed.add_field(name='Phase', value=phase)
        if ranklist.is_rated:
            embed.add_field(name='Deltas', value=deltas_status)

        if wait_msg:
            try:
                await wait_msg.delete()
            except:
                pass

        await ctx.send(embed=embed)
        paginator.paginate(self.bot,
                           ctx.channel,
                           pages,
                           wait_time=_STANDINGS_PAGINATE_WAIT_TIME)
Exemplo n.º 18
0
Arquivo: graphs.py Projeto: cfalas/TLE
    async def _rating_hist(self, ctx, ratings, mode, binsize, title):
        if mode not in ('log', 'normal'):
            await ctx.send(embed=discord_common.embed_alert(
                'Mode should be either `log` or `normal`.'))
            return

        ratings = [max(r, 0) for r in ratings]

        assert 100 % binsize == 0  # because bins is semi-hardcoded
        bins = 39 * 100 // binsize

        colors = []
        low, high = 0, binsize * bins
        for rank in cf.RATED_RANKS:
            for r in range(max(rank.low, low), min(rank.high, high), binsize):
                colors.append('#' + '%06x' % rank.color_embed)
        assert len(
            colors) == bins, f'Expected {bins} colors, got {len(colors)}'

        height = [0] * bins
        for r in ratings:
            height[r // binsize] += 1

        csum = 0
        cent = []
        users = sum(height)
        for h in height:
            csum += h
            cent.append(round(100 * csum / users))

        x = [k * binsize for k in range(bins)]
        label = [f'{r} ({c})' for r, c in zip(x, cent)]

        l, r = 0, bins - 1
        while not height[l]:
            l += 1
        while not height[r]:
            r -= 1
        x = x[l:r + 1]
        cent = cent[l:r + 1]
        label = label[l:r + 1]
        colors = colors[l:r + 1]
        height = height[l:r + 1]

        plt.clf()
        fig = plt.figure(figsize=(15, 5))

        plt.xticks(rotation=45)
        plt.xlim(l * binsize - binsize // 2, r * binsize + binsize // 2)
        plt.bar(x,
                height,
                binsize * 0.9,
                color=colors,
                linewidth=0,
                tick_label=label,
                log=(mode == 'log'))
        plt.xlabel('Rating')
        plt.ylabel('Number of users')

        discord_file = _get_current_figure_as_file()
        embed = discord_common.cf_color_embed(title=title)
        discord_common.attach_image(embed, discord_file)
        discord_common.set_author_footer(embed, ctx.author)
        await ctx.send(embed=embed, file=discord_file)
        plt.close(fig)