示例#1
0
class NoContestsAvailableException(Exception):

    onlineJudges = OnlineJudges()

    def __init__(self, oj=None):
        self.oj = oj

    def __str__(self):
        if self.oj is None:
            return 'Sorry, there are not upcoming contests currently available.'
        return 'Sorry, there are not upcoming contests from %s currently available.' % self.onlineJudges.formal_names[
            self.oj]
示例#2
0
class ProblemCog(commands.Cog):
    problems_by_points = {'dmoj':{}, 'codeforces':{}, 'atcoder':{}, 'peg':{}}
    dmoj_problems = None
    cf_problems = None
    at_problems = None
    cses_problems = {}
    # peg_problems = {}
    szkopul_problems = {}
    dmoj_sessions = {}
    cf_sessions = {}
    dmoj_user_suggests = {}
    cf_user_suggests = {}
    szkopul_page = 1
    language = Language()
    onlineJudges = OnlineJudges()

    def __init__(self, bot):
        self.bot = bot

        self.refresh_dmoj_problems.start()
        self.refresh_cf_problems.start()
        self.refresh_atcoder_problems.start()
        self.refresh_cses_problems.start()
        # self.refresh_peg_problems.start()
        self.refresh_szkopul_problems.start()
        self.logout_offline.start()

    def parse_dmoj_problems(self, problems):
        if problems is not None:
            self.dmoj_problems = problems
            self.problems_by_points['dmoj'] = {}
            for name, details in problems.items():
                if details['points'] not in self.problems_by_points['dmoj']:
                    self.problems_by_points['dmoj'][details['points']] = {}
                self.problems_by_points['dmoj'][details['points']][name] = details

    def parse_cf_problems(self, cf_data):
        if cf_data is not None:
            try:
                self.cf_problems = cf_data['result']['problems']
                self.problems_by_points['codeforces'] = {}
                for details in self.cf_problems:
                    if 'points' in details.keys():
                        if details['points'] not in self.problems_by_points['codeforces']:
                            self.problems_by_points['codeforces'][details['points']] = []
                        self.problems_by_points['codeforces'][details['points']].append(details)
            except KeyError:
                pass

    def parse_atcoder_problems(self, problems):
        if problems is not None:
            self.atcoder_problems = problems
            self.problems_by_points['atcoder'] = {}
            for details in problems:
                if details['point']:
                    if details['point'] not in self.problems_by_points['atcoder']:
                        self.problems_by_points['atcoder'][details['point']] = []
                    self.problems_by_points['atcoder'][details['point']].append(details)

    def parse_cses_problems(self, problems):
        if problems.status_code == 200:
            soup = bs.BeautifulSoup(problems.text, 'lxml')
            task_lists = soup.findAll('ul', attrs={'class' : 'task-list'})
            task_groups = soup.findAll('h2')
            self.cses_problems = {}
            for index in range(1, len(task_groups)):
                tasks = task_lists[index].findAll('li', attrs={'class' : 'task'})
                for task in tasks:
                    name = task.find('a').contents[0]
                    url = 'https://cses.fi' + task.find('a').attrs['href']
                    id = url.split('/')[-1]
                    rate = task.find('span', attrs={'class' : 'detail'}).contents[0]
                    group = task_groups[index].contents[0]
                    cses_data = {
                        'id': id,
                        'name': name,
                        'url': url,
                        'rate': rate,
                        'group': group
                    }
                    self.cses_problems[id] = cses_data

    # def parse_peg_problems(self, problems):
    #     if problems.status_code == 200:
    #         soup = bs.BeautifulSoup(problems.text, 'lxml')
    #         table = soup.find('table', attrs={'class' : 'nicetable stripes'}).findAll('tr')
    #         self.peg_problems = {}
    #         self.problems_by_points['peg'] = {}
    #         for prob in range(1, len(table)):
    #             values = table[prob].findAll('td')
    #             name = values[0].find('a').contents[0]
    #             url = 'https://wcipeg.com/problem/' + values[1].contents[0]
    #             points_value = values[2].contents[0]
    #             partial = 'p' in points_value
    #             points = int(points_value.replace('p', ''))
    #             p_users = values[3].find('a').contents[0]
    #             ac_rate = values[4].contents[0]
    #             date = values[5].contents[0]
    #             peg_data = {
    #                 'name': name,
    #                 'url': url,
    #                 'partial': partial,
    #                 'points': points,
    #                 'users': p_users,
    #                 'ac_rate': ac_rate,
    #                 'date': date
    #             }
    #             self.peg_problems[name] = peg_data
    #             if points not in self.problems_by_points['peg']:
    #                 self.problems_by_points['peg'][points] = []
    #             self.problems_by_points['peg'][points].append(peg_data)

    def parse_szkopul_problems(self):
        problems = requests.get('https://szkopul.edu.pl/problemset/?page=%d' % self.szkopul_page)
        if problems.status_code == 200:
            soup = bs.BeautifulSoup(problems.text, 'lxml')
            rows = soup.findAll('tr')
            if self.szkopul_page == 1:
                self.szkopul_problems = {}
            if len(rows) == 1:
                return
            for row in rows:
                data = row.findAll('td')
                if data == []:
                    continue
                id = data[0].contents[0]
                title = data[1].find('a').contents[0]
                url = 'https://szkopul.edu.pl' + data[1].find('a').attrs['href']
                tags = []
                for tag in data[2].findAll('a'):
                    tags.append(tag.contents[0])
                submitters = data[3].contents[0]
                problem_data = {
                    'id': id,
                    'title': title,
                    'url': url,
                    'tags': tags,
                    'submitters': submitters
                }
                if int(submitters) > 0:
                    problem_data['percent_correct'] = data[4].contents[0]
                    problem_data['average'] = data[5].contents[0]
                self.szkopul_problems[id] = problem_data
            self.szkopul_page += 1

    def embed_dmoj_problem(self, name, prob, suggested=False):
        embed = discord.Embed()
        url = 'https://dmoj.ca/problem/' + name
        embed.set_thumbnail(url='https://raw.githubusercontent.com/kevinjycui/Practice-Bot/master/assets/dmoj-thumbnail.png')
        embed.add_field(name='Points', value=prob['points'], inline=False)
        embed.add_field(name='Partials', value=('Yes' if prob['partial'] else 'No'), inline=False)
        embed.add_field(name='Group', value=prob['group'], inline=False)
        return ('[:thumbsup: SUGGESTED] ' if suggested else '') + prob['name'], url, embed

    def embed_cf_problem(self, prob, suggested=False):
        embed = discord.Embed()
        url = 'https://codeforces.com/problemset/problem/' + str(prob['contestId']) + '/' + str(prob['index'])
        embed.set_thumbnail(url='https://raw.githubusercontent.com/kevinjycui/Practice-Bot/master/assets/cf-thumbnail.png')
        embed.add_field(name='Type', value=prob['type'], inline=False)
        if 'points' in prob.keys():
            embed.add_field(name='Points', value=prob['points'], inline=False)
        if 'rating' in prob.keys():
            embed.add_field(name='Rating', value=prob['rating'], inline=False)
        embed.add_field(name='Tags', value='||'+', '.join(prob['tags'])+'||', inline=False)
        return ('[:thumbsup: SUGGESTED] ' if suggested else '') + prob['name'], url, embed

    def embed_atcoder_problem(self, prob):
        embed = discord.Embed()
        url = 'https://atcoder.jp/contests/' + prob['contest_id'] + '/tasks/' + prob['id']
        embed.set_thumbnail(url='https://raw.githubusercontent.com/kevinjycui/Practice-Bot/master/assets/at-thumbnail.png')
        if prob['point']:
            embed.add_field(name='Points', value=prob['point'], inline=False)
        embed.add_field(name='Solver Count', value=prob['solver_count'], inline=False)
        return prob['title'], url, embed

    def embed_cses_problem(self, prob):
        embed = discord.Embed()
        embed.set_thumbnail(url='https://raw.githubusercontent.com/kevinjycui/Practice-Bot/master/assets/cses-thumbnail.png')
        embed.add_field(name='Success Rate', value=prob['rate'], inline=False)
        embed.add_field(name='Group', value='||' + prob['group'] + '||', inline=False)
        return prob['name'], prob['url'], embed

    # def embed_peg_problem(self, prob):
    #     embed = discord.Embed()
    #     embed.set_thumbnail(url='https://raw.githubusercontent.com/kevinjycui/Practice-Bot/master/assets/peg-thumbnail.png')
    #     embed.add_field(name='Points', value=prob['points'], inline=False)
    #     embed.add_field(name='Partials', value=('Yes' if prob['partial'] else 'No'), inline=False)
    #     embed.add_field(name='Users', value=prob['users'], inline=False)
    #     embed.add_field(name='AC Rate', value=prob['ac_rate'], inline=False)
    #     embed.add_field(name='Date Added', value=prob['date'], inline=False)
    #     return prob['name'], prob['url'], embed

    def embed_szkopul_problem(self, prob):
        embed = discord.Embed()
        embed.set_thumbnail(url='https://raw.githubusercontent.com/kevinjycui/Practice-Bot/master/assets/szkopul-thumbnail.png')
        if len(prob['tags']) > 0:
            embed.add_field(name='Tags', value=', '.join(prob['tags']), inline=False)
        embed.add_field(name='Submitters', value=prob['submitters'], inline=False)
        if 'percent_correct' in prob:
            embed.add_field(name='% Correct', value=prob['percent_correct'], inline=False)
        if 'average' in prob:
            embed.add_field(name='Average', value=prob['average'], inline=False)
        return prob['title'], prob['url'], embed

    def get_problem(self, oj, contest_id=None, problem_id=None, szkopul_url=''):

        oj = self.onlineJudges.get_oj(oj)
        if oj != 'szkopul' and problem_id is None:
            raise ProblemNotFoundException
        
        if oj == 'dmoj':
            if problem_id not in self.dmoj_problems.keys():
                raise ProblemNotFoundException
            title, description, embed = self.embed_dmoj_problem(problem_id, self.dmoj_problems[problem_id])
            embed.title = title
            embed.description = description + ' (searched in %ss)' % str(round(self.bot.latency, 3))
            embed.timestamp = datetime.utcnow()
            return embed
        
        elif oj == 'codeforces':
            if contest_id is None:
                raise ProblemNotFoundException
            def is_problem(prob):
                return prob['contestId'] == int(contest_id) and prob['index'] == problem_id
            problist = list(filter(is_problem, self.cf_problems))
            if len(problist) == 0:
                raise ProblemNotFoundException
            title, description, embed = self.embed_cf_problem(problist[0])
            embed.title = title
            embed.description = description + ' (searched in %ss)' % str(round(self.bot.latency, 3))
            embed.timestamp = datetime.utcnow()
            return embed

        elif oj == 'atcoder':
            if contest_id is None:
                raise ProblemNotFoundException
            def is_problem(prob):
                return prob['contest_id'] == contest_id and prob['id'] == problem_id
            problist = list(filter(is_problem, self.atcoder_problems))
            if len(problist) == 0:
                raise ProblemNotFoundException
            title, description, embed = self.embed_atcoder_problem(problist[0])
            embed.title = title
            embed.description = description + ' (searched in %ss)' % str(round(self.bot.latency, 3))
            embed.timestamp = datetime.utcnow()
            return embed

        # elif oj == 'peg':
        #     def is_problem(prob):
        #         return prob['url'] == 'https://wcipeg.com/problem/' + problem_id
        #     problist = list(filter(is_problem, list(self.peg_problems.values())))
        #     if len(problist) == 0:
        #         raise ProblemNotFoundException
        #     title, description, embed = self.embed_peg_problem(problist[0])
        #     embed.title = title
        #     embed.description = description + ' (searched in %ss)' % str(round(self.bot.latency, 3))
        #     embed.timestamp = datetime.utcnow()
        #     return embed

        elif oj == 'cses':
            if problem_id not in self.cses_problems.keys():
                raise CSESProblemNotFoundException
            title, description, embed = self.embed_cses_problem(self.cses_problems[problem_id])
            embed.title = title
            embed.description = description + ' (searched in %ss)' % str(round(self.bot.latency, 3))
            embed.timestamp = datetime.utcnow()
            return embed

        elif oj == 'szkopul':
            if szkopul_url == '':
                raise ProblemNotFoundException
            def is_problem(prob):
                return prob['url'] == szkopul_url
            problist = list(filter(is_problem, list(self.szkopul_problems.values())))
            if len(problist) == 0:
                raise ProblemNotFoundException
            title, description, embed = self.embed_szkopul_problem(problist[0])
            embed.title = title
            embed.description = description + ' (searched in %ss)' % str(round(self.bot.latency, 3))
            embed.timestamp = datetime.utcnow()
            return embed

    def get_problem_from_url(self, url):
        components = url.split('/')
        if url[:24] == 'https://dmoj.ca/problem/' and len(components) == 5:
            return self.get_problem('dmoj', problem_id=components[4])
        elif url[:42] == 'https://codeforces.com/problemset/problem/' and len(components) == 7:
            return self.get_problem('codeforces', components[5], components[6])
        elif url[:28] == 'https://atcoder.jp/contests/' and len(components) == 7 and components[5] == 'tasks':
            return self.get_problem('atcoder', components[4], components[6])
        elif url[:27] == 'https://wcipeg.com/problem/' and len(components) == 5:
            return self.get_problem('peg', problem_id=components[4])
        elif url[:32] == 'https://cses.fi/problemset/task/' and len(components) == 6:
            return self.get_problem('cses', problem_id=components[5])
        elif url[:42] == 'https://szkopul.edu.pl/problemset/problem/':
            return self.get_problem('szkopul', szkopul_url=url)
        else:
            raise InvalidURLException

    def get_random_problem(self, oj=None, points=None, maximum=None, iden=None):
        if oj is None:
            oj = rand.choice(self.onlineJudges.judges)

        oj = self.onlineJudges.get_oj(oj)
        
        if oj == 'cses' and points is not None:
            raise InvalidParametersException(cses=True)

        temp_dmoj_problems = {}
        temp_cf_problems = []
        user_data = query.get_user(iden)
        suggestions_on = False

        if iden is not None:
            suggestions_on = user_data[iden]['can_suggest'] and points is None and ((
                    oj == 'dmoj' and user_data[iden]['dmoj'] is not None
                ) or (
                    oj == 'codeforces' and user_data[iden]['codeforces'] is not None
                ))
            if suggestions_on:
                if oj == 'dmoj':
                    if iden not in self.dmoj_user_suggests.keys():
                        self.dmoj_user_suggests[iden] = DMOJUserSuggester(user_data[iden]['dmoj'])
                    points, maximum = self.dmoj_user_suggests[iden].get_pp_range()
                elif oj == 'codeforces':
                    if iden not in self.cf_user_suggests.keys():
                        self.cf_user_suggests[iden] = CodeforcesUserSuggester(user_data[iden]['codeforces'])
                    points, maximum = self.cf_user_suggests[iden].get_pp_range()
                
            if not user_data[iden]['can_repeat']:
                if oj == 'dmoj' and user_data[iden]['dmoj'] is not None:
                    user_response = json_get('https://dmoj.ca/api/user/info/%s' % user_data[iden]['dmoj'])
                    if user_response is not None:
                        if points is None:
                            for name, prob in list(self.dmoj_problems.items()):
                                if name not in user_response['solved_problems']:
                                    temp_dmoj_problems[name] = prob
                        else:
                            temp_dmoj_problems['dmoj'] = {}
                            for point in list(self.problems_by_points['dmoj']):
                                temp_dmoj_problems['dmoj'][point] = {}
                                for name, prob in list(self.problems_by_points['dmoj'][point].items()):
                                    if name not in user_response['solved_problems']:
                                        temp_dmoj_problems['dmoj'][point][name] = prob
                        if temp_dmoj_problems == {}:
                            raise InvalidParametersException()
                elif oj == 'codeforces' and user_data[iden]['codeforces'] is not None:
                    response = requests.get('https://codeforces.com/api/user.status?handle=' + user_data[iden]['codeforces'])
                    if response.status_code != 200 or response.json()['status'] != 'OK':
                        return None
                    solved = []
                    for sub in response.json()['result']:
                        if sub['verdict'] == 'OK':
                            if 'contestId' in sub['problem']:
                                solved.append((sub['problem']['contestId'], sub['problem']['index']))
                            elif 'problemsetName' in sub['problem']:
                                solved.append((sub['problem']['problemsetName'], sub['problem']['index']))
                    if points is None:
                        temp_cf_problems = list(filter(lambda prob: (prob.get('contestId', prob.get('problemsetName')), prob['index']) not in solved, self.cf_problems))
                    else:
                        temp_cf_problems = {'codeforces': {}}
                        for point in list(self.problems_by_points['codeforces']):
                            temp_cf_problems['codeforces'][point] = list(filter(lambda prob: (prob['contestId'], prob['index']) not in solved, self.problems_by_points['codeforces'][point]))
                    if temp_cf_problems == [] or (type(temp_cf_problems) is dict and temp_cf_problems['codeforces'] == {}):
                        raise InvalidParametersException()

        if temp_dmoj_problems != {}:
            problem_list = temp_dmoj_problems
        elif temp_cf_problems != []:
            problem_list = temp_cf_problems
        elif points is None:
            if oj == 'dmoj':
                problem_list = self.dmoj_problems
            elif oj == 'codeforces':
                problem_list = self.cf_problems
        else:
            problem_list = self.problems_by_points
                                    
        if points is not None:
            if not points.isdigit():
                raise InvalidQueryException()
            points = int(points)

        if maximum is not None:
            if not maximum.isdigit():
                raise InvalidQueryException()
            maximum = int(maximum)
            possibilities = []
            for point in list(problem_list[oj].keys()):
                if point >= points and point <= maximum:
                    possibilities.append(point)
            if len(possibilities) == 0:
                if suggestions_on and oj == 'dmoj':
                    while len(possibilities) == 0:
                        self.dmoj_user_suggests[iden].expand_pp_range()
                        points, maximum = map(int, self.dmoj_user_suggests[iden].get_pp_range())
                        for point in list(problem_list[oj].keys()):
                            if point >= points and point <= maximum:
                                possibilities.append(point)
                        if points <= 1 and maximum >= 50 and len(possibilities) == 0:
                            raise InvalidParametersException()
                elif suggestions_on and oj == 'codeforces':
                    while len(possibilities) == 0:
                        self.cf_user_suggests[iden].expand_pp_range()
                        points, maximum = map(int, self.cf_user_suggests[iden].get_pp_range())
                        for point in list(problem_list[oj].keys()):
                            if point >= points and point <= maximum:
                                possibilities.append(point)
                        if points <= 1 and maximum >= 50 and len(possibilities) == 0:
                            raise InvalidParametersException()
                else:
                    raise InvalidParametersException()
            points = rand.choice(possibilities)
            
        if oj == 'dmoj':
            if not self.dmoj_problems:
                raise OnlineJudgeHTTPException('DMOJ')
                
            if points is None:
                name, prob = rand.choice(list(problem_list.items()))
            elif points in problem_list['dmoj'] and len(problem_list['dmoj'][points]) > 0:
                name, prob = rand.choice(list(problem_list['dmoj'][points].items()))
            else:
                raise InvalidParametersException()
            if iden is not None:
                user_data[iden]['last_dmoj_problem'] = name
                query.update_user(iden, 'last_dmoj_problem', name)
            return self.embed_dmoj_problem(name, prob, suggestions_on)
            
        elif oj == 'codeforces':
            if not self.cf_problems:
                raise OnlineJudgeHTTPException('Codeforces')
                return
            if points is None:
                prob = rand.choice(problem_list)
            elif points in problem_list['codeforces']:
                prob = rand.choice(problem_list['codeforces'][points])
            else:
                raise InvalidParametersException()
            return self.embed_cf_problem(prob, suggestions_on)

        elif oj == 'atcoder':
            if not self.atcoder_problems:
                raise OnlineJudgeHTTPException('AtCoder')

            if points is None:
                prob = rand.choice(self.atcoder_problems)
            elif points in self.problems_by_points['atcoder']:
                prob = rand.choice(self.problems_by_points['atcoder'][points])
            else:
                raise InvalidParametersException()
            return self.embed_atcoder_problem(prob)

        # elif oj == 'peg':
        #     if not self.peg_problems:
        #         raise OnlineJudgeHTTPException('WCIPEG')
        #     if points is None:
        #         prob = rand.choice(list(self.peg_problems.values()))
        #     elif points in self.problems_by_points['peg']:
        #         prob = rand.choice(list(self.problems_by_points['peg'][points]))
        #     else:
        #         raise InvalidParametersException()
        #     return self.embed_peg_problem(prob)

        elif oj == 'cses':
            prob = rand.choice(list(self.cses_problems.values()))
            return self.embed_cses_problem(prob)

        elif oj == 'szkopul':
            prob = rand.choice(list(self.szkopul_problems.values()))
            return self.embed_szkopul_problem(prob)
        
        else:
            raise NoSuchOJException(oj)

    def check_existing_user(self, user):
        query.insert_ignore_user(user.id)

    def check_existing_server(self, server):
        query.insert_ignore_server(server.id)

    @commands.command(aliases=['r'])
    @commands.bot_has_permissions(embed_links=True)
    async def random(self, ctx, oj=None, points=None, maximum=None):
        self.check_existing_user(ctx.message.author)
        if isinstance(oj, str) and (oj.lower() == 'peg' or oj.lower() == 'wcipeg'):
            await ctx.send(ctx.message.author.display_name + ', Notice: Support for WCIPEG has been discontinued as **PEG Judge shut down at the end of July 2020**\nhttps://wcipeg.com/announcement/9383')
            return
        try:
            title, description, embed = self.get_random_problem(oj, points, maximum, ctx.message.author.id)
            embed.title = title
            embed.description = description + ' (searched in %ss)' % str(round(self.bot.latency, 3))
            embed.timestamp = datetime.utcnow()
            # if rand.randint(0, 30) == 0 and isinstance(oj, str) and (oj.lower() == 'dmoj' or oj.lower() == 'codeforces' or oj.lower() == 'cf'):
            #     prefix = await self.bot.command_prefix(self.bot, ctx.message)
            #     await ctx.send('Pro tip: Try out the new command, `%stogglesuggest` to turn on personalised suggested problems for DMOJ and Codeforces!' % prefix)
            await ctx.send('Requested problem for ' + ctx.message.author.display_name, embed=embed)
        except IndexError:
            await ctx.send(ctx.message.author.display_name + ', No problem was found. This may be due to the bot updating the problem cache. Please wait a moment, then try again.')
        except NoSuchOJException:
            await ctx.send(ctx.message.author.display_name + ', Invalid query. The online judge must be one of the following: %s.' % str(self.onlineJudges))
        except InvalidParametersException as e:
            await ctx.send(ctx.message.author.display_name + ', ' + str(e))
        except OnlineJudgeHTTPException as e:
            await ctx.send(ctx.message.author.display_name + ', There seems to be a problem with %s. Please try again later :shrug:' % str(e))
        except InvalidQueryException:
            await ctx.send(ctx.message.author.display_name + ', Invalid query. Make sure your points are positive integers.')

    @commands.command(aliases=['toggleRepeat'])
    async def togglerepeat(self, ctx):
        self.check_existing_user(ctx.message.author)
        user_data = query.get_user(ctx.message.author.id)
        for account in self.onlineJudges.accounts:
            if user_data[ctx.message.author.id][account] is not None:
                user_data[ctx.message.author.id]['can_repeat'] = not user_data[ctx.message.author.id]['can_repeat']
                query.update_user(ctx.message.author.id, 'can_repeat', user_data[ctx.message.author.id]['can_repeat'])
                prefix = await self.bot.command_prefix(self.bot, ctx.message)
                if user_data[ctx.message.author.id]['can_repeat']:
                    await ctx.send(ctx.message.author.display_name + ', random problems will now contain already solved problems')
                else:
                    await ctx.send(ctx.message.author.display_name + ', random problems will no longer contain already solved problems')
                return
        await ctx.send(ctx.message.author.display_name + ', You are not linked to any accounts')

    @commands.command(aliases=['toggleSuggest'])
    async def togglesuggest(self, ctx):
        self.check_existing_user(ctx.message.author)
        user_data = query.get_user(ctx.message.author.id)
        for account in self.onlineJudges.accounts:
            if user_data[ctx.message.author.id][account] is not None:
                user_data[ctx.message.author.id]['can_suggest'] = not user_data[ctx.message.author.id]['can_suggest']
                query.update_user(ctx.message.author.id, 'can_suggest', user_data[ctx.message.author.id]['can_suggest'])
                prefix = await self.bot.command_prefix(self.bot, ctx.message)
                if user_data[ctx.message.author.id]['can_suggest']:
                    await ctx.send(ctx.message.author.display_name + ', random problems will now be suggested based on your existing solves')
                else:
                    await ctx.send(ctx.message.author.display_name + ', random problems will no longer be suggested based on your existing solves')
                return
        await ctx.send(ctx.message.author.display_name + ', You are not linked to any accounts')
        
    @commands.command(aliases=['toggleCountry'])
    async def togglecountry(self, ctx, code=''):
        try:
            country_object = Country(code)
            self.check_existing_user(ctx.message.author)
            user_data = query.get_user(ctx.message.author.id)
            prev_country = user_data[ctx.message.author.id]['country']
            user_data[ctx.message.author.id]['country'] = country_object.country
            query.update_user(ctx.message.author.id, 'country', user_data[ctx.message.author.id]['country'])
            if prev_country is not None and prev_country != country_object.country:
                await ctx.send(ctx.message.author.display_name + ', Changed your country from %s to %s.' % (str(Country(prev_country)), str(country_object)))
            else:
                await ctx.send(ctx.message.author.display_name + ', Set your country to %s.' % str(country_object))
        except InvalidCountryException:
            prefix = await self.bot.command_prefix(self.bot, ctx.message)
            await ctx.send(ctx.message.author.display_name + ', Sorry, could not find that country. Search for a country using the name (e.g. `%stogglecountry Finland`, `%stogglecountry "United States"`) or the 2 character ISO code (e.g. `%stogglecountry FI`))' % (prefix, prefix, prefix))

    @commands.command(aliases=['u', 'profile', 'whois'])
    @commands.bot_has_permissions(embed_links=True)
    async def user(self, ctx, user: discord.User=None):
        if user is None:
            user = ctx.message.author
        self.check_existing_user(user)
        user_data = query.get_user(user.id)
        embed = discord.Embed(title=user.display_name)
        embed.timestamp = datetime.utcnow()
        empty = True
        if user_data[user.id]['dmoj'] is not None:
            embed.add_field(name='DMOJ', value='https://dmoj.ca/user/%s' % user_data[user.id]['dmoj'], inline=False)
            empty = False
        if user_data[user.id]['codeforces'] is not None:
            embed.add_field(name='Codeforces', value='https://codeforces.com/profile/%s' % user_data[user.id]['codeforces'], inline=False)
            empty = False
        if user_data[user.id]['country'] is not None:
            embed.add_field(name='Country', value=str(Country(user_data[user.id]['country'])), inline=False)
            empty = False
        if empty:
            embed.description = 'No accounts linked...'
        await ctx.send('Requested profile by ' + ctx.message.author.display_name, embed=embed)

    @commands.command(aliases=['s'])
    async def submit(self, ctx, problem, lang, *, source=None):
        if ctx.message.author.id not in self.dmoj_sessions.keys():
            prefix = await self.bot.command_prefix(self.bot, ctx.message)
            await ctx.send(ctx.message.author.display_name + ', You are not logged in to a DMOJ account with submission permissions (this could happen if you last logged in a long time ago or have recently gone offline). Please use command `%sconnect dmoj <token>` (your DMOJ API token can be found by going to https://dmoj.ca/edit/profile/ and selecting the __Generate__ or __Regenerate__ option next to API Token). Note: The connect command will ONLY WORK IN DIRECT MESSAGE. Please do not share this token with anyone else.' % prefix)
            return
        user_session = self.dmoj_sessions[ctx.message.author.id]
        if not self.language.languageExists(lang):
            await ctx.send(ctx.message.author.display_name + ', That language is not available. The available languages are as followed: ```%s```' % ', '.join(self.language.getLanguages()))
            return
        try:
            if source is None and len(ctx.message.attachments) > 0:
                f = requests.get(ctx.message.attachments[0].url)
                source = f.content
            self.check_existing_user(ctx.message.author)
            user_data = query.get_user(ctx.message.author.id)
            if problem == '^' and user_data[ctx.message.author.id]['last_dmoj_problem'] is not None:
                problem = user_data[ctx.message.author.id]['last_dmoj_problem']
            id = user_session.submit(problem, self.language.getId(lang), source)
            response = user_session.getTestcaseStatus(id)
            responseText = str(response)
            if len(responseText) > 1950:
                responseText = responseText[1950:] + '\n(Result cut off to fit message length limit)'
            await ctx.send(ctx.message.author.display_name + ', ' + responseText + '\nTrack your submission here: https://dmoj.ca/submission/' + str(id))
        except InvalidSessionException:
            await ctx.send(ctx.message.author.display_name + ', Failed to connect, or problem not available. Make sure you are submitting to a valid problem, check your authentication, and try again.')
        except:
            await ctx.send(ctx.message.author.display_name + ', Error submitting to the problem. Report this using command `$suggest Submission to DMOJ failed`.')

    @tasks.loop(seconds=30)
    async def logout_offline(self):
        for guild in self.bot.guilds:
            for member in guild.members:
                if member.id in self.dmoj_sessions.keys() and member.status == discord.Status.offline:
                    await member.send('Attention! You have been logged out of the account %s due to being offline (Note that your account will still be linked to your Discord account, but will now be unable to submit to problems)' % self.dmoj_sessions.pop(member.id))

    @logout_offline.before_loop
    async def logout_offline_before(self):
        await self.bot.wait_until_ready()

    @tasks.loop(hours=3)
    async def refresh_dmoj_problems(self):
        self.parse_dmoj_problems(json_get('https://dmoj.ca/api/problem/list'))
        
    @refresh_dmoj_problems.before_loop
    async def refresh_dmoj_problems_before(self):
        await self.bot.wait_until_ready()

    @tasks.loop(hours=3)
    async def refresh_cf_problems(self):
        self.parse_cf_problems(json_get('https://codeforces.com/api/problemset.problems'))

    @refresh_cf_problems.before_loop
    async def refresh_cf_problems_before(self):
        await self.bot.wait_until_ready()

    @tasks.loop(hours=3)
    async def refresh_atcoder_problems(self):
        self.parse_atcoder_problems(json_get('https://kenkoooo.com/atcoder/resources/merged-problems.json'))

    @refresh_atcoder_problems.before_loop
    async def refresh_atcoder_problems_before(self):
        await self.bot.wait_until_ready()

    @tasks.loop(hours=3)
    async def refresh_cses_problems(self):
        self.parse_cses_problems(requests.get('https://cses.fi/problemset/list/'))

    @refresh_cses_problems.before_loop
    async def refresh_cses_problems_before(self):
        await self.bot.wait_until_ready()

    # @tasks.loop(hours=3)
    # async def refresh_peg_problems(self):
    #     self.parse_peg_problems(requests.get('https://wcipeg.com/problems/show%3D999999'))

    # @refresh_peg_problems.before_loop
    # async def refresh_peg_problems_before(self):
    #     await self.bot.wait_until_ready()

    @tasks.loop(hours=3)
    async def refresh_szkopul_problems(self):
        self.parse_szkopul_problems()

    @refresh_szkopul_problems.before_loop
    async def refresh_szkopul_problems_before(self):
        await self.bot.wait_until_ready()

    @commands.command()
    @commands.guild_only()
    async def tea(self, ctx, user: discord.User=None):
        if user is None:
            self.check_existing_user(ctx.message.author)
            user_data = query.get_user(ctx.message.author.id)
            if user_data[ctx.message.author.id]['tea'] == 1:
                await ctx.send(ctx.message.author.display_name + ', You have 1 cup of :tea:.')
            else:
                await ctx.send(ctx.message.author.display_name + ', You have ' + str(user_data[ctx.message.author.id]['tea']) + ' cups of :tea:.')
            return
        if user.id == ctx.message.author.id:
            await ctx.send(ctx.message.author.display_name + ', Sorry, cannot send :tea: to yourself!')
            return
        elif user.id == self.bot.user.id:
            await ctx.send(ctx.message.author.display_name + ', Thanks for the :tea:!')
            return
        self.check_existing_user(user)
        user_data = query.get_user(user.id)
        query.update_user(user.id, 'tea', user_data[user.id]['tea']+1)
        await ctx.send(ctx.message.author.display_name + ', sent a cup of :tea: to ' + user.mention)
示例#3
0
import re
import discord
from discord.ext import commands, tasks
import random as rand
import yaml
import cogs.dblapi as dblapi
import cogs.feedback as feedback
import cogs.problems_rankings as problems_rankings
import cogs.contests as contests
import cogs.searcher as searcher
from connector import mySQLConnection as query
from utils.onlinejudges import OnlineJudges, NoSuchOJException

onlineJudges = OnlineJudges()

is_ascii = lambda s: re.match('^[\x00-\x7F]+$', s) != None

try:
    config_file = open('config.yml')
except FileNotFoundError:
    config_file = open('example_config.yml')
finally:
    config = yaml.load(config_file, Loader=yaml.FullLoader)
    prefix = config['bot']['prefix']
    bot_token = config['bot']['token']
    DEBUG = config['bot']['debug']
    dbl_tokens = {}
    if 'dbl' in config:
        for dbl, dbl_token in list(config['dbl'].items()):
            dbl_tokens[dbl] = dbl_token
    bot_id = config['bot']['id']
示例#4
0
class ContestCog(commands.Cog):
    fetch_time = 0

    dmoj_contests = []
    cf_contests = []
    atcoder_contests = []

    dmoj_contest_titles = []
    cf_contest_titles = []
    atcoder_contest_titles = []

    contest_objects = []

    onlineJudges = OnlineJudges()

    def __init__(self, bot):
        self.bot = bot

        with open('data/contests.json', 'r', encoding='utf8',
                  errors='ignore') as f:
            prev_contest_data = json.load(f)
            self.contest_cache = []
            for data in prev_contest_data:
                self.contest_cache.append(Contest(data))

        self.refresh_contests.start()

    def get_random_contests(self, number):
        if len(self.contest_cache) == 0:
            raise NoContestsAvailableException()
        rand.shuffle(self.contest_cache)
        result = []
        for i in range(min(number, len(self.contest_cache))):
            result.append(self.contest_cache[i])
        return self.embed_multiple_contests(result)

    def get_contests_of_oj(self, oj):
        result = []
        for contest_object in self.contest_cache:
            if oj == contest_object.asdict()['oj']:
                result.append(contest_object)
        if len(result) == 0:
            raise NoContestsAvailableException(oj)
        return self.embed_multiple_contests(result, oj)

    def reset_contest(self, oj):
        if oj == 'dmoj':
            self.dmoj_contests = []
            self.dmoj_contest_titles = []
        elif oj == 'codeforces':
            self.cf_contests = []
            self.cf_contest_titles = []
        elif oj == 'atcoder':
            self.atcoder_contests = []
            self.atcoder_contest_titles = []

    def set_time(self):
        self.fetch_time = time()

    def parse_dmoj_contests(self, contests):
        if contests is not None:
            for contest in range(len(contests)):
                name, details = list(contests.items())[contest]
                if datetime.strptime(details['start_time'].replace(':', ''),
                                     '%Y-%m-%dT%H%M%S%z') > datetime.now(
                                         pytz.utc):
                    spec = json_get('https://dmoj.ca/api/contest/info/' + name)
                    url = 'https://dmoj.ca/contest/' + name
                    contest_data = {
                        'title':
                        ':trophy: %s' % details['name'],
                        'description':
                        url,
                        'oj':
                        'dmoj',
                        'thumbnail':
                        'https://raw.githubusercontent.com/kevinjycui/Practice-Bot/master/assets/dmoj-thumbnail.png',
                        'Start Time':
                        datetime.strptime(
                            details['start_time'].replace(':', ''),
                            '%Y-%m-%dT%H%M%S%z').strftime('%Y-%m-%d %H:%M:%S'),
                        'End Time':
                        datetime.strptime(
                            details['end_time'].replace(':', ''),
                            '%Y-%m-%dT%H%M%S%z').strftime('%Y-%m-%d %H:%M:%S')
                    }
                    if details['time_limit']:
                        contest_data['Time Limit'] = details['time_limit']
                    if len(details['labels']) > 0:
                        contest_data['Labels'] = ', '.join(details['labels'])
                    contest_data['Rated'] = 'Yes' if spec['is_rated'] else 'No'
                    contest_data['Format'] = spec['format']['name']
                    if contest_data['title'] not in self.dmoj_contest_titles:
                        self.dmoj_contest_titles.append(contest_data['title'])
                        self.dmoj_contests.append(Contest(contest_data))
            self.dmoj_contests = list(set(self.dmoj_contests))

    def parse_cf_contests(self, contests):
        if contests is not None and contests['status'] == 'OK':
            for contest in range(len(contests.get('result', []))):
                details = contests['result'][contest]
                if details['phase'] == 'BEFORE':
                    url = 'https://codeforces.com/contest/' + str(
                        details['id'])
                    contest_data = {
                        'title':
                        ':trophy: %s' % details['name'],
                        'description':
                        url,
                        'oj':
                        'codeforces',
                        'thumbnail':
                        'https://raw.githubusercontent.com/kevinjycui/Practice-Bot/master/assets/cf-thumbnail.png',
                        'Type':
                        details['type'],
                        'Start Time':
                        datetime.utcfromtimestamp(
                            details['startTimeSeconds']).strftime(
                                '%Y-%m-%d %H:%M:%S'),
                        'Time Limit':
                        '%s:%s:%s' %
                        (str(details['durationSeconds'] //
                             (24 * 3600)).zfill(2),
                         str(details['durationSeconds'] %
                             (24 * 3600) // 3600).zfill(2),
                         str(details['durationSeconds'] % 3600 // 60).zfill(2))
                    }
                    if contest_data['title'] not in self.cf_contest_titles:
                        self.cf_contest_titles.append(contest_data['title'])
                        self.cf_contests.append(Contest(contest_data))
            self.cf_contests = list(set(self.cf_contests))

    def parse_atcoder_contests(self, contests):
        if contests is not None:
            for contest in range(len(contests)):
                details = contests[contest]
                if details['startTimeSeconds'] > time():
                    url = 'https://atcoder.jp/contests/' + details['id']
                    contest_data = {
                        'title':
                        ':trophy: %s' % details['title'].replace(
                            '\n', '').replace('\t', '').replace('◉', ''),
                        'description':
                        url,
                        'oj':
                        'atcoder',
                        'thumbnail':
                        'https://raw.githubusercontent.com/kevinjycui/Practice-Bot/master/assets/at-thumbnail.png',
                        'Start Time':
                        datetime.utcfromtimestamp(
                            details['startTimeSeconds']).strftime(
                                '%Y-%m-%d %H:%M:%S'),
                        'Time Limit':
                        '%s:%s:%s' % (str(details['durationSeconds'] //
                                          (24 * 3600)).zfill(2),
                                      str(details['durationSeconds'] %
                                          (24 * 3600) // 3600).zfill(2),
                                      str(details['durationSeconds'] % 3600 //
                                          60).zfill(2)),
                        'Rated Range':
                        details['ratedRange']
                    }
                    if contest_data[
                            'title'] not in self.atcoder_contest_titles:
                        self.atcoder_contest_titles.append(
                            contest_data['title'])
                        self.atcoder_contests.append(Contest(contest_data))
            self.atcoder_contests = list(set(self.atcoder_contests))

    def embed_contest(self, contest):
        embed = discord.Embed(title=contest.asdict()['title'],
                              description=contest.asdict()['description'])
        embed.set_thumbnail(url=contest.asdict()['thumbnail'])
        for key in list(contest.asdict().keys()):
            if key not in ('title', 'description', 'thumbnail', 'oj'):
                embed.add_field(name=key,
                                value=contest.asdict()[key],
                                inline=False)
        return embed

    def embed_multiple_contests(self, contests, oj=None, new=False):
        if len(contests) == 0:
            return None
        if len(contests) == 1:
            return self.embed_contest(contests[0])
        if oj is not None:
            embed = discord.Embed(title='%d %s%s Contests' %
                                  (len(contests), ' New' if new else '',
                                   self.onlineJudges.formal_names[oj]))
            embed.set_thumbnail(url=self.onlineJudges.thumbnails[oj])
        else:
            embed = discord.Embed(title='%d%s Contests' %
                                  (len(contests), ' New' if new else ''))
        for contest in sorted(contests):
            embed.add_field(name=contest.asdict()['title'],
                            value=contest.asdict()['description'],
                            inline=(len(contests) > 6))
        return embed

    def generate_stream(self):
        self.contest_objects = list(
            set(self.dmoj_contests + self.cf_contests + self.atcoder_contests))

    def update_contest_cache(self):
        with open('data/contests.json', 'w') as json_file:
            prev_contest_data = []
            for contest in self.contest_cache:
                prev_contest_data.append(contest.asdict())
            json.dump(prev_contest_data, json_file)

    @commands.command(aliases=['c'])
    @commands.bot_has_permissions(embed_links=True)
    async def contests(self, ctx, numstr='1'):
        try:
            if numstr == 'all':
                number = len(self.contest_cache)
                contestList = self.get_random_contests(number)
            elif numstr.isdigit():
                number = int(numstr)
                contestList = self.get_random_contests(number)
            elif self.onlineJudges.oj_exists(numstr):
                oj = self.onlineJudges.get_oj(numstr)
                if oj not in self.onlineJudges.contest_judges:
                    await ctx.send(
                        ctx.message.author.display_name +
                        ', Sorry, contests for that site are not available yet or contests are not applicable to that site.'
                    )
                    return
                contestList = self.get_contests_of_oj(oj)
            await ctx.send(
                ctx.message.author.display_name +
                ', Here are %d upcoming contest(s). Last fetched, %d minutes ago'
                % (1 if numstr == '1' else len(contestList.fields),
                   (time() - self.fetch_time) // 60),
                embed=contestList)
        except NoContestsAvailableException as e:
            await ctx.send(ctx.message.author.display_name + ', ' + str(e))
        except NoSuchOJException:
            await ctx.send(
                ctx.message.author.display_name +
                ', Invalid query. The online judge must be one of the following: %s.'
                % str(self.onlineJudges))

    @commands.command()
    @commands.has_permissions(manage_channels=True)
    @commands.guild_only()
    async def sub(self, ctx, channel: discord.TextChannel = None):
        determiner = 'That'
        if channel is None:
            channel = ctx.message.channel
            determiner = 'This'
        if query.exists('subscriptions_contests', 'channel_id', channel.id):
            await ctx.send(
                ctx.message.author.display_name +
                ', %s channel is already subscribed to contest notifications.'
                % determiner)
            return
        query.sub_channel(channel.id)
        await ctx.send(ctx.message.author.display_name + ', ' +
                       channel.mention +
                       ' subscribed to contest notifications.')

    @commands.command()
    @commands.guild_only()
    async def subs(self, ctx):
        clist = ctx.message.author.display_name + ', Contest notification channels in this server:\n'
        for text_channel in ctx.message.guild.text_channels:
            if query.exists('subscriptions_contests', 'channel_id',
                            text_channel.id):
                clist += text_channel.mention + '\n'
        if clist == ctx.message.author.display_name + ', Contest notification channels in this server:\n':
            await ctx.send(
                ctx.message.author.display_name +
                ', There are no channels subscribed to contest notifications in this server :slight_frown:'
            )
        else:
            await ctx.send(clist)

    @commands.command()
    @commands.has_permissions(manage_channels=True)
    @commands.guild_only()
    async def unsub(self, ctx, channel: discord.TextChannel = None):
        determiner = 'That'
        if channel is None:
            channel = ctx.message.channel
            determiner = 'This'
        if not query.exists('subscriptions_contests', 'channel_id',
                            channel.id):
            await ctx.send(
                ctx.message.author.display_name +
                ', %s channel is already not subscribed to contest notifications.'
                % determiner)
            return
        query.unsub_channel(channel.id)
        await ctx.send(ctx.message.author.display_name + ', ' +
                       channel.mention +
                       ' is no longer a contest notification channel.')

    def is_upcoming(self, contest):
        return datetime.strptime(
            contest.asdict()['Start Time'],
            '%Y-%m-%d %H:%M:%S') > datetime.now() - timedelta(days=7)

    @tasks.loop(minutes=5)
    async def refresh_contests(self):
        try:
            self.reset_contest('dmoj')
            self.parse_dmoj_contests(
                json_get('https://dmoj.ca/api/contest/list'))
        except:
            pass

        try:
            self.reset_contest('codeforces')
            self.parse_cf_contests(
                json_get('https://codeforces.com/api/contest.list'))
        except:
            pass

        try:
            self.reset_contest('atcoder')
            self.parse_atcoder_contests(
                json_get('https://atcoder-api.appspot.com/contests'))
        except:
            pass

        self.set_time()
        self.generate_stream()

        new_contests = list(
            set(self.contest_objects).difference(set(self.contest_cache)))

        for channel_id in query.get_all_subs():
            try:
                channel = self.bot.get_channel(channel_id)
                await channel.send(
                    embed=self.embed_multiple_contests(new_contests, new=True))
            except:
                pass

        self.contest_cache = list(
            filter(
                self.is_upcoming,
                list(set(self.contest_objects).union(set(
                    self.contest_cache)))))
        self.update_contest_cache()

    @refresh_contests.before_loop
    async def check_contests_before(self):
        await self.bot.wait_until_ready()
示例#5
0
class ProblemCog(commands.Cog):
    problems_by_points = {'dmoj': {}, 'codeforces': {}, 'atcoder': {}}

    dmoj_problems = {}
    cf_problems = None
    at_problems = None
    cses_problems = []
    szkopul_problems = {}
    leetcode_problems = []
    leetcode_problems_paid = []

    dmoj_sessions = {}
    cf_sessions = {}

    dmoj_user_suggests = {}
    cf_user_suggests = {}

    szkopul_page = 1
    language = Language()
    onlineJudges = OnlineJudges()

    statuses = {
        'dmoj': 0,
        'codeforces': 0,
        'atcoder': 0,
        'cses': 0,
        'szkopul': 0,
        'leetcode': 0
    }
    fetch_times = {
        'dmoj': 0,
        'codeforces': 0,
        'atcoder': 0,
        'cses': 0,
        'szkopul': 0,
        'leetcode': 0
    }

    def __init__(self, bot):
        self.bot = bot
        self.refresh_dmoj_problems.start()
        self.refresh_cf_problems.start()
        self.refresh_atcoder_problems.start()
        self.refresh_leetcode_problems.start()
        self.refresh_cses_problems.start()
        self.refresh_szkopul_problems.start()

    @commands.command()
    async def oj(self, ctx, oj: str = ''):
        if oj == '':
            status_list = '```'
            for oj in self.onlineJudges.problem_judges:
                status_list += '%s status: %s. Last fetched, %d minutes ago\n' % (
                    self.onlineJudges.formal_names[oj],
                    'OK' if self.statuses[oj] == 1 else 'Unable to connect',
                    (time() - self.fetch_times[oj]) // 60)
            await ctx.send(status_list[:-1] + '```')
        else:
            try:
                oj = self.onlineJudges.get_oj(oj)
                if oj not in self.onlineJudges.problem_judges:
                    raise NoSuchOJException(oj)
                await ctx.send(
                    '```%s status: %s. Last fetched, %d minutes ago```' %
                    (self.onlineJudges.formal_names[oj],
                     'OK' if self.statuses[oj] == 1 else 'Unable to connect',
                     (time() - self.fetch_times[oj]) // 60))
            except NoSuchOJException:
                await ctx.send(
                    ctx.message.author.display_name +
                    ', Sorry, no online judge found. Search only for online judges used for getting problems this bot '
                    + self.onlineJudges.problem_judges_str())

    async def parse_dmoj_problems(self):
        try:
            problem_req = await webc.webget_json(
                'https://dmoj.ca/api/v2/problems')
            problems = problem_req['data']['objects']
            self.statuses['dmoj'] = 1
            self.dmoj_problems = {}
            for problem in problems:
                self.dmoj_problems[problem['code']] = problem
            self.problems_by_points['dmoj'] = {}
            for name, details in self.dmoj_problems.items():
                if details['points'] not in self.problems_by_points['dmoj']:
                    self.problems_by_points['dmoj'][details['points']] = {}
                self.problems_by_points['dmoj'][
                    details['points']][name] = details
        except Exception as e:
            self.statuses['dmoj'] = 0
            raise e

    def parse_cf_problems(self):
        try:
            problems = requests.get(
                'https://codeforces.com/api/problemset.problems').json()
            self.statuses['codeforces'] = 1
            self.cf_problems = problems['result']['problems']
            self.problems_by_points['codeforces'] = {}
            for details in self.cf_problems:
                if 'rating' in details.keys():
                    if details['rating'] not in self.problems_by_points[
                            'codeforces']:
                        self.problems_by_points['codeforces'][
                            details['rating']] = []
                    self.problems_by_points['codeforces'][
                        details['rating']].append(details)
        except Exception as e:
            self.statuses['codeforces'] = 0
            raise e

    def parse_atcoder_problems(self):
        try:
            problems = requests.get(
                'https://kenkoooo.com/atcoder/resources/merged-problems.json'
            ).json()
            self.statuses['atcoder'] = 1
            self.atcoder_problems = problems
            self.problems_by_points['atcoder'] = {}
            for details in problems:
                if details['point']:
                    if details['point'] not in self.problems_by_points[
                            'atcoder']:
                        self.problems_by_points['atcoder'][
                            details['point']] = []
                    self.problems_by_points['atcoder'][
                        details['point']].append(details)
        except Exception as e:
            self.statuses['atcoder'] = 0
            raise e

    def parse_cses_problems(self):
        try:
            problems = requests.get('https://cses.fi/problemset/list/').text
            self.statuses['cses'] = 1
            self.cses_problems = []
            soup = bs.BeautifulSoup(problems, 'lxml')
            task_lists = soup.find_all('ul', attrs={'class': 'task-list'})
            task_groups = soup.find_all('h2')
            for index in range(1, len(task_groups)):
                tasks = task_lists[index].find_all('li',
                                                   attrs={'class': 'task'})
                for task in tasks:
                    name = task.find('a').contents[0]
                    url = 'https://cses.fi' + task.find('a').attrs['href']
                    id = url.split('/')[-1]
                    rate = task.find('span', attrs={
                        'class': 'detail'
                    }).contents[0]
                    group = task_groups[index].contents[0]
                    cses_data = {
                        'id': id,
                        'name': name,
                        'url': url,
                        'rate': rate,
                        'group': group
                    }
                    self.cses_problems.append(cses_data)
        except Exception as e:
            self.statuses['cses'] = 0
            raise e

    def parse_szkopul_problems(self):
        try:
            problems = requests.get(
                'https://szkopul.edu.pl/problemset/?page=%d' %
                self.szkopul_page).text
            self.statuses['szkopul'] = 1
            soup = bs.BeautifulSoup(problems, 'lxml')
            rows = soup.find_all('tr')
            if len(rows) == 1:
                self.szkopul_problems = [
                    p for p in self.szkopul_problems if p['updated']
                ]
                return
            if self.szkopul_page == 1:
                for problem in self.szkopul_problems:
                    problem['updated'] = False
            for row in rows:
                data = row.find_all('td')
                if data == []:
                    continue
                id = data[0].contents[0]
                title = data[1].find('a').contents[0]
                url = 'https://szkopul.edu.pl' + data[1].find(
                    'a').attrs['href']
                tags = []
                for tag in data[2].find_all('a'):
                    tags.append(tag.contents[0])
                submitters = data[3].contents[0]
                problem_data = {
                    'id': id,
                    'title': title,
                    'url': url,
                    'tags': tags,
                    'submitters': submitters,
                    'updated': True
                }
                if int(submitters) > 0:
                    problem_data['percent_correct'] = data[4].contents[0]
                    problem_data['average'] = data[5].contents[0]
                self.szkopul_problems[id] = problem_data
            self.szkopul_page += 1
        except Exception as e:
            self.statuses['szkopul'] = 0
            raise e

    def parse_leetcode_problems(self):
        try:
            problems = requests.get(
                'https://leetcode.com/api/problems/algorithms/').json()
            self.statuses['leetcode'] = 1
            self.leetcode_problems_paid = []
            self.leetcode_problems = []
            problemlist = problems['stat_status_pairs']
            for problem in problemlist:
                id = problem['stat']['frontend_question_id']
                title = problem['stat']['question__title']
                url = 'https://leetcode.com/problems/' + problem['stat'][
                    'question__title_slug']
                total_acs = problem['stat']['total_acs']
                total_submitted = problem['stat']['total_submitted']
                level = problem['difficulty']['level']
                paid = problem['paid_only']
                problem_data = {
                    'id': id,
                    'title': title,
                    'url': url,
                    'total_acs': total_acs,
                    'total_submitted': total_submitted,
                    'level': level,
                    'paid': paid
                }
                if paid:
                    self.leetcode_problems_paid.append(problem_data)
                else:
                    self.leetcode_problems.append(problem_data)
        except Exception as e:
            self.statuses['leetcode'] = 0
            raise e

    def embed_dmoj_problem(self, name, prob, suggested=False):
        embed = discord.Embed()
        embed.colour = self.onlineJudges.colours['dmoj']
        url = 'https://dmoj.ca/problem/' + name
        embed.set_thumbnail(url=self.onlineJudges.thumbnails['dmoj'])
        embed.add_field(name='Points', value=prob['points'], inline=False)
        embed.add_field(name='Partials',
                        value=('Yes' if prob['partial'] else 'No'),
                        inline=False)
        embed.add_field(name='Group', value=prob['group'], inline=False)
        embed.add_field(name='Types',
                        value='||' + ', '.join(prob['types']) + '||',
                        inline=False)
        return ('[:thumbsup: SUGGESTED] '
                if suggested else '') + prob['name'], url, embed

    def embed_cf_problem(self, prob, suggested=False):
        embed = discord.Embed()
        embed.colour = self.onlineJudges.colours['codeforces']
        url = 'https://codeforces.com/problemset/problem/' + str(
            prob['contestId']) + '/' + str(prob['index'])
        embed.set_thumbnail(url=self.onlineJudges.thumbnails['codeforces'])
        embed.add_field(name='Type', value=prob['type'], inline=False)
        if 'points' in prob.keys():
            embed.add_field(name='Points', value=prob['points'], inline=False)
        if 'rating' in prob.keys():
            embed.add_field(name='Rating', value=prob['rating'], inline=False)
        embed.add_field(name='Tags',
                        value='||' + ', '.join(prob['tags']) + '||',
                        inline=False)
        return ('[:thumbsup: SUGGESTED] '
                if suggested else '') + prob['name'], url, embed

    def embed_atcoder_problem(self, prob):
        embed = discord.Embed()
        embed.colour = self.onlineJudges.colours['atcoder']
        url = 'https://atcoder.jp/contests/' + prob[
            'contest_id'] + '/tasks/' + prob['id']
        embed.set_thumbnail(url=self.onlineJudges.thumbnails['atcoder'])
        if prob['point']:
            embed.add_field(name='Points', value=prob['point'], inline=False)
        embed.add_field(name='Solver Count',
                        value=prob['solver_count'],
                        inline=False)
        return prob['title'], url, embed

    def embed_cses_problem(self, prob):
        embed = discord.Embed()
        embed.colour = self.onlineJudges.colours['cses']
        embed.set_thumbnail(url=self.onlineJudges.thumbnails['cses'])
        embed.add_field(name='Success Rate', value=prob['rate'], inline=False)
        embed.add_field(name='Group',
                        value='||' + prob['group'] + '||',
                        inline=False)
        return prob['name'], prob['url'], embed

    def embed_szkopul_problem(self, prob):
        embed = discord.Embed()
        embed.colour = self.onlineJudges.colours['szkopul']
        embed.set_thumbnail(url=self.onlineJudges.thumbnails['szkopul'])
        if len(prob['tags']) > 0:
            embed.add_field(name='Tags',
                            value=', '.join(prob['tags']),
                            inline=False)
        embed.add_field(name='Submitters',
                        value=prob['submitters'],
                        inline=False)
        if 'percent_correct' in prob:
            embed.add_field(name='% Correct',
                            value=prob['percent_correct'],
                            inline=False)
        if 'average' in prob:
            embed.add_field(name='Average',
                            value=prob['average'],
                            inline=False)
        return prob['title'], prob['url'], embed

    def embed_leetcode_problem(self, prob):
        embed = discord.Embed()
        embed.colour = self.onlineJudges.colours['leetcode']
        embed.set_thumbnail(url=self.onlineJudges.thumbnails['leetcode'])
        embed.add_field(name='Total ACs',
                        value=prob['total_acs'],
                        inline=False)
        embed.add_field(name='Total Submitted',
                        value=prob['total_submitted'],
                        inline=False)
        embed.add_field(name='Level', value=prob['level'], inline=False)
        embed.add_field(name='Paid?',
                        value='Yes' if prob['paid'] else 'No',
                        inline=False)
        return prob['title'], prob['url'], embed

    async def get_random_problem(self,
                                 oj=None,
                                 points=None,
                                 maximum=None,
                                 iden=None,
                                 paid=False):
        if oj is None:
            oj = rand.choice(self.onlineJudges.problem_judges)

        oj = self.onlineJudges.get_oj(oj)

        if oj == 'cses' and points is not None:
            raise InvalidParametersException(cses=True)
        elif oj == 'szkopul' and points is not None:
            raise InvalidParametersException(szkopul=True)

        temp_dmoj_problems = {}
        temp_cf_problems = []
        user_data = query.get_user(iden)
        suggestions_on = False

        if iden is not None:
            suggestions_on = user_data[iden][
                'can_suggest'] and points is None and (
                    (oj == 'dmoj' and user_data[iden]['dmoj'] is not None) or
                    (oj == 'codeforces'
                     and user_data[iden]['codeforces'] is not None))
            if suggestions_on:
                if oj == 'dmoj':
                    if iden not in self.dmoj_user_suggests.keys():
                        self.dmoj_user_suggests[iden] = DMOJUserSuggester(
                            user_data[iden]['dmoj'])
                        await self.dmoj_user_suggests[iden].update_pp_range()
                    points, maximum = self.dmoj_user_suggests[
                        iden].get_pp_range()
                elif oj == 'codeforces':
                    if iden not in self.cf_user_suggests.keys():
                        self.cf_user_suggests[iden] = CodeforcesUserSuggester(
                            user_data[iden]['codeforces'])
                        await self.cf_user_suggests[iden].update_pp_range()
                    points, maximum = self.cf_user_suggests[iden].get_pp_range(
                    )

            if not user_data[iden]['can_repeat']:
                if oj == 'dmoj' and user_data[iden]['dmoj'] is not None:
                    user_response = await webc.webget_json(
                        'https://dmoj.ca/api/user/info/%s' %
                        user_data[iden]['dmoj'])
                    if points is None:
                        for name, prob in list(self.dmoj_problems.items()):
                            if name not in user_response['solved_problems']:
                                temp_dmoj_problems[name] = prob
                    else:
                        temp_dmoj_problems['dmoj'] = {}
                        for point in list(self.problems_by_points['dmoj']):
                            temp_dmoj_problems['dmoj'][point] = {}
                            for name, prob in list(
                                    self.problems_by_points['dmoj']
                                [point].items()):
                                if name not in user_response[
                                        'solved_problems']:
                                    temp_dmoj_problems['dmoj'][point][
                                        name] = prob
                    if temp_dmoj_problems == {}:
                        raise InvalidParametersException()
                elif oj == 'codeforces' and user_data[iden][
                        'codeforces'] is not None:
                    response = await webc.webget_json(
                        'https://codeforces.com/api/user.status?handle=' +
                        user_data[iden]['codeforces'])
                    if response['status'] != 'OK':
                        return None
                    solved = []
                    for sub in response['result']:
                        if sub['verdict'] == 'OK':
                            if 'contestId' in sub['problem']:
                                solved.append((sub['problem']['contestId'],
                                               sub['problem']['index']))
                            elif 'problemsetName' in sub['problem']:
                                solved.append(
                                    (sub['problem']['problemsetName'],
                                     sub['problem']['index']))
                    if points is None:
                        temp_cf_problems = list(
                            filter(
                                lambda prob: (prob.get(
                                    'contestId', prob.get('problemsetName')),
                                              prob['index']) not in solved,
                                self.cf_problems))
                    else:
                        temp_cf_problems = {'codeforces': {}}
                        for point in list(
                                self.problems_by_points['codeforces']):
                            temp_cf_problems['codeforces'][point] = list(
                                filter(
                                    lambda prob: (prob['contestId'], prob[
                                        'index']) not in solved,
                                    self.problems_by_points['codeforces']
                                    [point]))
                    if temp_cf_problems == [] or (
                            type(temp_cf_problems) is dict
                            and temp_cf_problems['codeforces'] == {}):
                        raise InvalidParametersException()

        if temp_dmoj_problems != {}:
            problem_list = temp_dmoj_problems
        elif temp_cf_problems != []:
            problem_list = temp_cf_problems
        elif points is None:
            if oj == 'dmoj':
                problem_list = self.dmoj_problems
            elif oj == 'codeforces':
                problem_list = self.cf_problems
        else:
            problem_list = self.problems_by_points

        if points is not None:
            if not points.isdigit():
                raise InvalidQueryException()
            points = int(points)

        if maximum is not None:
            if not maximum.isdigit():
                raise InvalidQueryException()
            maximum = int(maximum)

            if oj != 'leetcode':
                possibilities = []
                for point in list(problem_list[oj].keys()):
                    if point >= points and point <= maximum:
                        possibilities.append(point)
                if len(possibilities) == 0:
                    if suggestions_on and oj == 'dmoj':
                        while len(possibilities) == 0:
                            self.dmoj_user_suggests[iden].expand_pp_range()
                            points, maximum = map(
                                int,
                                self.dmoj_user_suggests[iden].get_pp_range())
                            for point in list(problem_list[oj].keys()):
                                if point >= points and point <= maximum:
                                    possibilities.append(point)
                            if points <= 1 and maximum >= 50 and len(
                                    possibilities) == 0:
                                raise InvalidParametersException()
                    elif suggestions_on and oj == 'codeforces':
                        while len(possibilities) == 0:
                            self.cf_user_suggests[iden].expand_pp_range()
                            points, maximum = map(
                                int,
                                self.cf_user_suggests[iden].get_pp_range())
                            for point in list(problem_list[oj].keys()):
                                if point >= points and point <= maximum:
                                    possibilities.append(point)
                            if points <= 1 and maximum >= 50 and len(
                                    possibilities) == 0:
                                raise InvalidParametersException()
                    else:
                        raise InvalidParametersException()
                points = rand.choice(possibilities)

        if oj == 'dmoj':
            if not self.dmoj_problems:
                raise OnlineJudgeHTTPException('DMOJ')

            if points is None:
                name, prob = rand.choice(list(problem_list.items()))
            elif points in problem_list['dmoj'] and len(
                    problem_list['dmoj'][points]) > 0:
                name, prob = rand.choice(
                    list(problem_list['dmoj'][points].items()))
            else:
                raise InvalidParametersException()
            if iden is not None:
                user_data[iden]['last_dmoj_problem'] = name
                query.update_user(iden, 'last_dmoj_problem', name)
            return self.embed_dmoj_problem(name, prob, suggestions_on)

        elif oj == 'codeforces':
            if not self.cf_problems:
                raise OnlineJudgeHTTPException('Codeforces')
                return
            if points is None:
                prob = rand.choice(problem_list)
            elif points in problem_list['codeforces']:
                prob = rand.choice(problem_list['codeforces'][points])
            else:
                raise InvalidParametersException()
            return self.embed_cf_problem(prob, suggestions_on)

        elif oj == 'atcoder':
            if not self.atcoder_problems:
                raise OnlineJudgeHTTPException('AtCoder')

            if points is None:
                prob = rand.choice(self.atcoder_problems)
            elif points in self.problems_by_points['atcoder']:
                prob = rand.choice(self.problems_by_points['atcoder'][points])
            else:
                raise InvalidParametersException()
            return self.embed_atcoder_problem(prob)

        elif oj == 'cses':
            prob = rand.choice(self.cses_problems)
            return self.embed_cses_problem(prob)

        elif oj == 'szkopul':
            prob = rand.choice(list(self.szkopul_problems.values()))
            return self.embed_szkopul_problem(prob)

        elif oj == 'leetcode':
            if points is not None:
                if points not in (1, 2, 3):
                    raise InvalidParametersException(leetcode=True)
                if maximum is not None:
                    if maximum not in (1, 2, 3) or maximum < points:
                        raise InvalidParametersException(leetcode=True)
                    else:
                        points = rand.randint(points, maximum)

                def is_level(prob):
                    return prob['level'] == points

                if paid:
                    prob = rand.choice(
                        list(
                            filter(
                                is_level, self.leetcode_problems +
                                self.leetcode_problems_paid)))
                else:
                    prob = rand.choice(
                        list(filter(is_level, self.leetcode_problems)))
            elif paid:
                prob = rand.choice(self.leetcode_problems +
                                   self.leetcode_problems_paid)
            else:
                prob = rand.choice(self.leetcode_problems)
            return self.embed_leetcode_problem(prob)

        else:
            raise NoSuchOJException(oj)

    def check_existing_user(self, user):
        query.insert_ignore_user(user.id)

    def check_existing_server(self, server):
        query.insert_ignore_server(server.id)

    @commands.command(aliases=['r'])
    @commands.bot_has_permissions(embed_links=True)
    async def random(self, ctx, oj=None, points=None, maximum=None):
        self.check_existing_user(ctx.message.author)
        if isinstance(oj, str) and (oj.lower() == 'peg'
                                    or oj.lower() == 'wcipeg'):
            await ctx.send(
                ctx.message.author.display_name +
                ', Notice: Support for WCIPEG has been discontinued as **PEG Judge shut down at the end of July 2020**\nhttps://wcipeg.com/announcement/9383'
            )
            return
        try:
            title, description, embed = await self.get_random_problem(
                oj, points, maximum, ctx.message.author.id)
            embed.title = title
            embed.description = description + ' (searched in %ss)' % str(
                round(self.bot.latency, 3))
            embed.timestamp = datetime.utcnow()
            await ctx.send('Requested problem for ' +
                           ctx.message.author.display_name,
                           embed=embed)
        except IndexError:
            await ctx.send(
                ctx.message.author.display_name +
                ', No problem was found. This may be due to the bot updating the problem cache. Please wait a moment, then try again.'
            )
        except NoSuchOJException:
            await ctx.send(
                ctx.message.author.display_name +
                ', Invalid query. The online judge must be one of the following: %s.'
                % self.onlineJudges.problem_judges_str())
        except InvalidParametersException as e:
            await ctx.send(ctx.message.author.display_name + ', ' + str(e))
        except OnlineJudgeHTTPException as e:
            await ctx.send(
                ctx.message.author.display_name +
                ', There seems to be a problem with %s. Please try again later :shrug:'
                % str(e))
        except InvalidQueryException:
            await ctx.send(
                ctx.message.author.display_name +
                ', Invalid query. Make sure your points are positive integers.'
            )

    @commands.command(aliases=['toggleRepeat', 'tr'])
    async def togglerepeat(self, ctx):
        self.check_existing_user(ctx.message.author)
        user_data = query.get_user(ctx.message.author.id)
        for account in self.onlineJudges.accounts:
            if user_data[ctx.message.author.id][account] is not None:
                user_data[ctx.message.author.id]['can_repeat'] = not user_data[
                    ctx.message.author.id]['can_repeat']
                query.update_user(
                    ctx.message.author.id, 'can_repeat',
                    user_data[ctx.message.author.id]['can_repeat'])
                prefix = await self.bot.command_prefix(self.bot, ctx.message)
                if user_data[ctx.message.author.id]['can_repeat']:
                    await ctx.send(
                        ctx.message.author.display_name +
                        ', random problems will now contain already solved problems'
                    )
                else:
                    await ctx.send(
                        ctx.message.author.display_name +
                        ', random problems will no longer contain already solved problems'
                    )
                return
        await ctx.send(ctx.message.author.display_name +
                       ', You are not linked to any accounts')

    @commands.command(aliases=['toggleSuggest', 'ts'])
    async def togglesuggest(self, ctx):
        self.check_existing_user(ctx.message.author)
        user_data = query.get_user(ctx.message.author.id)
        for account in self.onlineJudges.accounts:
            if user_data[ctx.message.author.id][account] is not None:
                user_data[
                    ctx.message.author.id]['can_suggest'] = not user_data[
                        ctx.message.author.id]['can_suggest']
                query.update_user(
                    ctx.message.author.id, 'can_suggest',
                    user_data[ctx.message.author.id]['can_suggest'])
                prefix = await self.bot.command_prefix(self.bot, ctx.message)
                if user_data[ctx.message.author.id]['can_suggest']:
                    await ctx.send(
                        ctx.message.author.display_name +
                        ', random problems will now be suggested based on your existing solves'
                    )
                else:
                    await ctx.send(
                        ctx.message.author.display_name +
                        ', random problems will no longer be suggested based on your existing solves'
                    )
                return
        await ctx.send(ctx.message.author.display_name +
                       ', You are not linked to any accounts')

    @commands.command(aliases=['toggleCountry', 'togglecountry', 'setCountry'])
    async def setcountry(self, ctx, code=''):
        try:
            country_object = Country(code)
            self.check_existing_user(ctx.message.author)
            user_data = query.get_user(ctx.message.author.id)
            prev_country = user_data[ctx.message.author.id]['country']
            user_data[
                ctx.message.author.id]['country'] = country_object.country
            query.update_user(ctx.message.author.id, 'country',
                              user_data[ctx.message.author.id]['country'])
            if prev_country is not None and prev_country != country_object.country:
                await ctx.send(
                    ctx.message.author.display_name +
                    ', Changed your country from %s to %s.' %
                    (str(Country(prev_country)), str(country_object)))
            else:
                await ctx.send(ctx.message.author.display_name +
                               ', Set your country to %s.' %
                               str(country_object))
        except InvalidCountryException:
            prefix = await self.bot.command_prefix(self.bot, ctx.message)
            await ctx.send(
                ctx.message.author.display_name +
                ', Sorry, could not find that country. Search for a country using the name (e.g. `%stogglecountry Finland`, `%stogglecountry "United States"`) or the 2 character ISO code (e.g. `%stogglecountry FI`))'
                % (prefix, prefix, prefix))

    @commands.command(aliases=['u', 'profile', 'whois'])
    @commands.bot_has_permissions(embed_links=True)
    async def user(self, ctx, user: discord.User = None):
        if user is None:
            user = ctx.message.author

        self.check_existing_user(user)
        user_data = query.get_user(user.id)

        embed = discord.Embed(title=user.display_name)
        embed.timestamp = datetime.utcnow()
        embed.colour = discord.Colour(int('0000ff', 16))

        empty = True
        if user_data[user.id]['dmoj'] is not None:
            embed.add_field(name='DMOJ',
                            value='https://dmoj.ca/user/%s' %
                            user_data[user.id]['dmoj'],
                            inline=False)
            empty = False
        if user_data[user.id]['codeforces'] is not None:
            embed.add_field(name='Codeforces',
                            value='https://codeforces.com/profile/%s' %
                            user_data[user.id]['codeforces'],
                            inline=False)
            empty = False
        if user_data[user.id]['country'] is not None:
            embed.add_field(name='Country',
                            value=str(Country(user_data[user.id]['country'])),
                            inline=False)
        if empty:
            embed.description = 'No accounts linked...'
        elif user.id == ctx.message.author.id:
            embed.add_field(
                name='Can repeat',
                value=str(user_data[user.id]['can_repeat'] == 1) +
                ' (If true, problems you have already solved on sites where your account is linked will show up when you request for random problems)',
                inline=False)
            embed.add_field(
                name='Can suggest',
                value=str(user_data[user.id]['can_suggest'] == 1) +
                ' (If true, suggested problems based on your points on sites where your account is linked will show up when you request for random problems)',
                inline=False)
        await ctx.send('Requested profile by ' +
                       ctx.message.author.display_name,
                       embed=embed)

    @commands.command(aliases=['si'])
    @commands.bot_has_permissions(embed_links=True)
    async def serverinfo(self, ctx):
        if ctx.message.guild is None:
            await ctx.send(
                ctx.message.author.display_name +
                ', You can only request for server info within a server!')
            return

        query.insert_ignore_server(ctx.message.guild.id)
        server_data = query.get_server(ctx.message.guild.id)

        embed = discord.Embed(title=ctx.message.guild.name)
        embed.timestamp = datetime.utcnow()
        embed.set_thumbnail(url=ctx.message.guild.icon_url)
        embed.colour = discord.Colour(int('ffd300', 16))

        embed.add_field(
            name='Nickname sync',
            value=str(server_data[ctx.message.guild.id]['nickname_sync'] == 1)
            +
            ' (If true, nicknames will be set automatically based on the sync source)',
            inline=False)
        embed.add_field(
            name='Role sync',
            value=str(server_data[ctx.message.guild.id]['role_sync'] == 1) +
            ' (If true, roles will be set automatically based on the sync source)',
            inline=False)
        if server_data[ctx.message.guild.id]['nickname_sync'] or server_data[
                ctx.message.guild.id]['role_sync']:
            embed.add_field(
                name='Sync source',
                value=server_data[ctx.message.guild.id]['sync_source'],
                inline=False)
        embed.add_field(
            name='Join message',
            value=str(server_data[ctx.message.guild.id]['join_message'] == 1) +
            ' (If true, bot will send a default join message whenever a new member joins your server)',
            inline=False)
        prefix = await self.bot.command_prefix(self.bot, ctx.message)
        embed.add_field(name='Server prefix', value=prefix, inline=False)
        clist = ''
        for text_channel in ctx.message.guild.text_channels:
            if query.exists('subscriptions_contests', 'channel_id',
                            text_channel.id):
                clist += text_channel.mention + '\n'
        embed.add_field(name='Contest notification channel(s)',
                        value='None' if clist == '' else clist,
                        inline=False)
        await ctx.send(ctx.message.author.display_name +
                       ', Here is your requested info!',
                       embed=embed)

    @commands.command(aliases=['s'])
    async def submit(self, ctx, problem, lang, *, source=None):
        if ctx.message.author.id not in self.dmoj_sessions.keys():
            prefix = await self.bot.command_prefix(self.bot, ctx.message)
            await ctx.send(
                ctx.message.author.display_name +
                ', You are not logged in to a DMOJ account with submission permissions (this could happen if you last logged in a long time ago or have recently gone offline). Please use command `%sconnect dmoj <token>` (your DMOJ API token can be found by going to https://dmoj.ca/edit/profile/ and selecting the __Generate__ or __Regenerate__ option next to API Token). Note: The connect command will ONLY WORK IN DIRECT MESSAGE. Please do not share this token with anyone else.'
                % prefix)
            return
        user_session = self.dmoj_sessions[ctx.message.author.id]
        if not self.language.languageExists(lang):
            await ctx.send(
                ctx.message.author.display_name +
                ', That language is not available. The available languages are as followed: ```%s```'
                % ', '.join(self.language.getLanguages()))
            return
        try:
            if source is None and len(ctx.message.attachments) > 0:
                f = webc.webget_text(ctx.message.attachments[0].url)
                source = f.content
            self.check_existing_user(ctx.message.author)
            user_data = query.get_user(ctx.message.author.id)
            if problem == '^' and user_data[
                    ctx.message.author.id]['last_dmoj_problem'] is not None:
                problem = user_data[ctx.message.author.id]['last_dmoj_problem']
            id = await user_session.submit(problem, self.language.getId(lang),
                                           source)
            response = await user_session.getTestcaseStatus(id)
            responseText = str(response)
            if len(responseText) > 1950:
                responseText = responseText[
                    1950:] + '\n(Result cut off to fit message length limit)'
            await ctx.send(
                ctx.message.author.display_name + ', ' + responseText +
                '\nTrack your submission here: https://dmoj.ca/submission/' +
                str(id))
        except InvalidDMOJSessionException:
            await ctx.send(
                ctx.message.author.display_name +
                ', Failed to connect, or problem not available. Make sure you are submitting to a valid problem, check your authentication, and try again.'
            )

    @commands.Cog.listener()
    async def on_member_update(self, before, after):
        if str(after.status) == 'offline' and str(
                before.status
        ) != 'offline' and after.id in self.dmoj_sessions.keys():
            await after.send(
                'Attention! You have been logged out of the account %s due to being offline (Note that your account will still be linked to your Discord account, but will now be unable to submit to problems)'
                % self.dmoj_sessions.pop(after.id))

    @tasks.loop(hours=23)
    async def refresh_dmoj_problems(self):
        await self.parse_dmoj_problems()
        self.fetch_times['dmoj'] = time()

    @refresh_dmoj_problems.before_loop
    async def refresh_dmoj_problems_before(self):
        await self.bot.wait_until_ready()

    @tasks.loop(hours=25)
    async def refresh_cf_problems(self):
        self.parse_cf_problems()
        self.fetch_times['codeforces'] = time()

    @refresh_cf_problems.before_loop
    async def refresh_cf_problems_before(self):
        await self.bot.wait_until_ready()

    @tasks.loop(hours=26)
    async def refresh_atcoder_problems(self):
        self.parse_atcoder_problems()
        self.fetch_times['atcoder'] = time()

    @refresh_atcoder_problems.before_loop
    async def refresh_atcoder_problems_before(self):
        await self.bot.wait_until_ready()

    @tasks.loop(hours=24 * 7)
    async def refresh_cses_problems(self):
        self.parse_cses_problems()
        self.fetch_times['cses'] = time()

    @refresh_cses_problems.before_loop
    async def refresh_cses_problems_before(self):
        await self.bot.wait_until_ready()

    @tasks.loop(hours=1, minutes=27)
    async def refresh_szkopul_problems(self):
        self.parse_szkopul_problems()
        self.fetch_times['szkopul'] = time()

    @refresh_szkopul_problems.before_loop
    async def refresh_szkopul_problems_before(self):
        await self.bot.wait_until_ready()

    @tasks.loop(hours=28)
    async def refresh_leetcode_problems(self):
        self.parse_leetcode_problems()
        self.fetch_times['leetcode'] = time()

    @refresh_leetcode_problems.before_loop
    async def refresh_leetcode_problems_before(self):
        await self.bot.wait_until_ready()

    @commands.command()
    @commands.guild_only()
    async def tea(self, ctx, user: discord.User = None):
        if user is None:
            self.check_existing_user(ctx.message.author)
            user_data = query.get_user(ctx.message.author.id)
            if user_data[ctx.message.author.id]['tea'] == 1:
                await ctx.send(ctx.message.author.display_name +
                               ', You have 1 cup of :tea:.')
            else:
                await ctx.send(ctx.message.author.display_name +
                               ', You have ' +
                               str(user_data[ctx.message.author.id]['tea']) +
                               ' cups of :tea:.')
            return
        if user.id == ctx.message.author.id:
            await ctx.send(ctx.message.author.display_name +
                           ', Sorry, cannot send :tea: to yourself!')
            return
        elif user.id == self.bot.user.id:
            await ctx.send(ctx.message.author.display_name +
                           ', Thanks for the :tea:!')
            return
        self.check_existing_user(user)
        user_data = query.get_user(user.id)
        query.update_user(user.id, 'tea', user_data[user.id]['tea'] + 1)
        await ctx.send(ctx.message.author.display_name +
                       ', sent a cup of :tea: to ' + user.mention)
示例#6
0
class ContestCog(commands.Cog):
    fetch_time = 0

    dmoj_contests = []
    cf_contests = []
    atcoder_contests = []
    leetcode_contests = []
    codechef_contests = []
    topcoder_contests = []

    contest_objects = []

    onlineJudges = OnlineJudges()

    def __init__(self, bot):
        self.bot = bot

        if not os.path.isfile('data/contests.json'):
            with open('data/contests.json',
                      'w+',
                      encoding='utf8',
                      errors='ignore') as f:
                json.dump([], f)

        with open('data/contests.json', 'r', encoding='utf8',
                  errors='ignore') as f:
            prev_contest_data = json.load(f)
            self.contest_cache = []
            for data in prev_contest_data:
                self.contest_cache.append(Contest(data))

        self.refresh_contests.start()

    def get_random_contests(self, number):
        upcoming_contests = list(filter(self.is_upcoming, self.contest_cache))
        if len(upcoming_contests) == 0:
            raise NoContestsAvailableException()
        rand.shuffle(upcoming_contests)
        result = []
        for i in range(min(number, len(upcoming_contests))):
            result.append(upcoming_contests[i])
        return self.embed_multiple_contests(result)

    def get_contests_of_oj(self, oj):
        upcoming_contests = list(filter(self.is_upcoming, self.contest_cache))
        result = []
        for contest_object in upcoming_contests:
            if oj == contest_object.asdict()['oj']:
                result.append(contest_object)
        if len(result) == 0:
            raise NoContestsAvailableException(oj)
        return self.embed_multiple_contests(result, oj)

    def reset_contest(self, oj):
        if oj == 'dmoj':
            self.dmoj_contests = []
        elif oj == 'codeforces':
            self.cf_contests = []
        elif oj == 'atcoder':
            self.atcoder_contests = []
        elif oj == 'codechef':
            self.codechef_contests = []
        elif oj == 'topcoder':
            self.topcoder_contests = []

    def set_time(self):
        self.fetch_time = time()

    def parse_dmoj_contests(self):
        contest_req = requests.get('https://dmoj.ca/api/v2/contests').json()
        contests = contest_req['data']['objects']
        for details in contests:
            name = details['key']
            if datetime.strptime(details['start_time'].replace(':', ''),
                                 '%Y-%m-%dT%H%M%S%z').timestamp() > time():
                spec = requests.get('https://dmoj.ca/api/v2/contest/' +
                                    name).json()['data']['object']
                url = 'https://dmoj.ca/contest/' + name
                contest_data = {
                    'title':
                    ':trophy: %s' % details['name'],
                    'description':
                    url,
                    'oj':
                    'dmoj',
                    'Start Time':
                    datetime.strptime(
                        details['start_time'].replace(':', ''),
                        '%Y-%m-%dT%H%M%S%z').strftime('%Y-%m-%d %H:%M:%S%z'),
                    'End Time':
                    datetime.strptime(
                        details['end_time'].replace(':', ''),
                        '%Y-%m-%dT%H%M%S%z').strftime('%Y-%m-%d %H:%M:%S%z')
                }
                if spec['time_limit'] is not None:
                    contest_data['Window'] = '%d:%d:%d' % (
                        spec['time_limit'] // (60 * 60), spec['time_limit'] %
                        (60 * 60) // 60, spec['time_limit'] % 60)
                if len(spec['tags']) > 0:
                    contest_data['Tags'] = ', '.join(spec['tags'])
                contest_data['Rated'] = 'Yes' if spec['is_rated'] else 'No'
                contest_data['Format'] = spec['format']['name']
                self.dmoj_contests.append(Contest(contest_data))

    def parse_cf_contests(self):
        contests = requests.get(
            'https://codeforces.com/api/contest.list').json()
        for contest in range(len(contests.get('result', []))):
            details = contests['result'][contest]
            if details['phase'] == 'BEFORE':
                url = 'https://codeforces.com/contest/' + str(details['id'])
                contest_data = {
                    'title':
                    ':trophy: %s' % details['name'],
                    'description':
                    url,
                    'oj':
                    'codeforces',
                    'Type':
                    details['type'],
                    'Start Time':
                    datetime.utcfromtimestamp(
                        details['startTimeSeconds']).strftime(
                            '%Y-%m-%d %H:%M:%S%z'),
                    'Duration':
                    '%s:%s:%s' %
                    (str(details['durationSeconds'] // (24 * 3600)).zfill(2),
                     str(details['durationSeconds'] %
                         (24 * 3600) // 3600).zfill(2),
                     str(details['durationSeconds'] % 3600 // 60).zfill(2))
                }
                self.cf_contests.append(Contest(contest_data))

    def parse_atcoder_contests(self):
        contests = requests.get('https://atcoder.jp/contests/?lang=en').text
        soup = bs.BeautifulSoup(contests, 'lxml')
        for contest in soup.find_all('table')[
                1 +
                len(soup.find_all('div', attrs={'id': 'contest-table-action'})
                    )].find('tbody').find_all('tr'):
            details = contest.find_all('td')
            if datetime.strptime(details[0].find('a').find('time').contents[0],
                                 '%Y-%m-%d %H:%M:%S%z').timestamp() > time():
                contest_data = {
                    'title':
                    ':trophy: %s' % details[1].find('a').contents[0],
                    'description':
                    'https://atcoder.jp' + details[1].find('a')['href'],
                    'oj':
                    'atcoder',
                    'Start Time':
                    datetime.strptime(
                        details[0].find('a').find('time').contents[0],
                        '%Y-%m-%d %H:%M:%S%z').strftime('%Y-%m-%d %H:%M:%S%z'),
                    'Duration':
                    details[2].contents[0] + ':00',
                    'Rated Range':
                    details[3].contents[0]
                }
                self.atcoder_contests.append(Contest(contest_data))

    def parse_external_contest_api(self):
        contests = requests.get('https://kontests.net/api/v1/all').json()
        for contest in contests:
            if contest['site'] == 'LeetCode':
                self.parse_leetcode_contest(contest)
            elif contest['site'] == 'CodeChef':
                self.parse_codechef_contest(contest)
            elif contest['site'] == 'TopCoder':
                self.parse_topcoder_contest(contest)

    def parse_leetcode_contest(self, contest):
        if contest['status'] == 'BEFORE':
            contest_data = {
                'title':
                ':trophy: %s' % contest['name'],
                'description':
                contest['url'],
                'oj':
                'leetcode',
                'Start Time':
                datetime.strptime(
                    contest['start_time'].split('.')[0],
                    '%Y-%m-%dT%H:%M:%S').strftime('%Y-%m-%d %H:%M:%S') +
                '+0000',
                'End Time':
                datetime.strptime(
                    contest['end_time'].split('.')[0],
                    '%Y-%m-%dT%H:%M:%S').strftime('%Y-%m-%d %H:%M:%S') +
                '+0000',
                'Duration':
                '%s:%s:%s' %
                (str(int(float(contest['duration'])) // (24 * 3600)).zfill(2),
                 str(int(float(contest['duration'])) %
                     (24 * 3600) // 3600).zfill(2),
                 str(int(float(contest['duration'])) % 3600 // 60).zfill(2))
            }
            self.leetcode_contests.append(Contest(contest_data))

    def parse_codechef_contest(self, contest):
        if contest['status'] == 'BEFORE':
            contest_data = {
                'title':
                ':trophy: %s' % contest['name'],
                'description':
                contest['url'].split('?')[0],
                'oj':
                'codechef',
                'Start Time':
                datetime.strptime(
                    contest['start_time'].split('.')[0],
                    '%Y-%m-%dT%H:%M:%S').strftime('%Y-%m-%d %H:%M:%S') +
                '+0000',
                'End Time':
                datetime.strptime(
                    contest['end_time'].split('.')[0],
                    '%Y-%m-%dT%H:%M:%S').strftime('%Y-%m-%d %H:%M:%S') +
                '+0000',
                'Duration':
                '%s:%s:%s' %
                (str(int(float(contest['duration'])) // (24 * 3600)).zfill(2),
                 str(int(float(contest['duration'])) %
                     (24 * 3600) // 3600).zfill(2),
                 str(int(float(contest['duration'])) % 3600 // 60).zfill(2))
            }
            self.codechef_contests.append(Contest(contest_data))

    def parse_topcoder_contest(self, contest):
        start_time = datetime.strptime(
            contest['start_time'].split('.')[0],
            '%Y-%m-%dT%H:%M:%S').strftime('%Y-%m-%d %H:%M:%S') + '+0000'
        if contest['status'] == 'BEFORE' and datetime.strptime(
                start_time, '%Y-%m-%d %H:%M:%S%z').timestamp() - time(
                ) <= 60 * 60 * 24 * 7:
            contest_data = {
                'title':
                ':trophy: %s' % contest['name'],
                'description':
                contest['url'],
                'oj':
                'topcoder',
                'Start Time':
                start_time,
                'End Time':
                datetime.strptime(
                    contest['end_time'].split('.')[0],
                    '%Y-%m-%dT%H:%M:%S').strftime('%Y-%m-%d %H:%M:%S') +
                '+0000',
                'Duration':
                '%s:%s:%s' %
                (str(int(float(contest['duration'])) // (24 * 3600)).zfill(2),
                 str(int(float(contest['duration'])) %
                     (24 * 3600) // 3600).zfill(2),
                 str(int(float(contest['duration'])) % 3600 // 60).zfill(2))
            }
            self.topcoder_contests.append(Contest(contest_data))

    def embed_contest(self, contest):
        embed = discord.Embed(title=contest.asdict()['title'],
                              description=contest.asdict()['description'])
        embed.set_thumbnail(
            url=self.onlineJudges.thumbnails[contest.asdict()['oj']])
        embed.colour = self.onlineJudges.colours[contest.asdict()['oj']]
        for key in list(contest.asdict().keys()):
            if key not in ('title', 'description', 'oj'):
                embed.add_field(name=key,
                                value=contest.asdict()[key],
                                inline=False)
        return embed

    def embed_multiple_contests(self, contests, oj=None, new=False):
        if len(contests) == 0:
            return None
        if len(contests) == 1:
            return self.embed_contest(contests[0])
        if oj is not None:
            embed = discord.Embed(title='%d %s%s Contests' %
                                  (len(contests), ' New' if new else '',
                                   self.onlineJudges.formal_names[oj]))
            embed.set_thumbnail(url=self.onlineJudges.thumbnails[oj])
            embed.colour = self.onlineJudges.colours[oj]
        else:
            embed = discord.Embed(title='%d%s Contests' %
                                  (len(contests), ' New' if new else ''))
        for contest in sorted(contests):
            embed.add_field(name=contest.asdict()['title'],
                            value=contest.asdict()['description'],
                            inline=(len(contests) > 6))
        return embed

    def generate_stream(self):
        self.contest_objects = list(
            set(self.dmoj_contests + self.cf_contests + self.atcoder_contests +
                self.leetcode_contests + self.codechef_contests +
                self.topcoder_contests))

    def update_contest_cache(self):
        with open('data/contests.json', 'w') as json_file:
            prev_contest_data = []
            for contest in self.contest_cache:
                prev_contest_data.append(contest.asdict())
            json.dump(prev_contest_data, json_file)

    @commands.command(aliases=['c'])
    @commands.bot_has_permissions(embed_links=True)
    async def contests(self, ctx, numstr='1'):
        try:
            if numstr == 'all':
                number = len(self.contest_cache)
                contestList = self.get_random_contests(number)
            elif numstr.isdigit():
                number = int(numstr)
                contestList = self.get_random_contests(number)
            elif self.onlineJudges.oj_exists(numstr):
                oj = self.onlineJudges.get_oj(numstr)
                if oj not in self.onlineJudges.contest_judges:
                    await ctx.send(
                        ctx.message.author.display_name +
                        ', Sorry, contests for that site are not available yet or contests are not applicable to that site.'
                    )
                    return
                contestList = self.get_contests_of_oj(oj)
            await ctx.send(
                ctx.message.author.display_name +
                ', Here are some upcoming contest(s). Last fetched, %d minutes ago'
                % ((time() - self.fetch_time) // 60),
                embed=contestList)
        except NoContestsAvailableException as e:
            await ctx.send(ctx.message.author.display_name + ', ' + str(e))
        except NoSuchOJException:
            await ctx.send(
                ctx.message.author.display_name +
                ', Invalid query. The online judge must be one of the following: %s.'
                % self.onlineJudges.contest_judges_str())

    @commands.command()
    @commands.has_permissions(manage_channels=True)
    @commands.guild_only()
    async def sub(self, ctx, channel: discord.TextChannel = None, *args):

        determiner = 'That'
        if channel is None:
            channel = ctx.message.channel
            determiner = 'This'

        exists = query.exists('subscriptions_contests', 'channel_id',
                              channel.id)

        old_sub_int = None
        old_sub_bin = None

        ojs = []
        for arg in args:
            try:
                oj = self.onlineJudges.get_oj(arg)
                if oj not in self.onlineJudges.contest_judges:
                    raise NoSuchOJException
                ojs.append(oj)
            except NoSuchOJException:
                await ctx.send(
                    ctx.message.author.display_name +
                    ', Invalid query. The online judges must be one of the following: %s.'
                    % self.onlineJudges.contest_judges_str())
                return

        sub_bin = '0' * len(self.onlineJudges.contest_judges)
        if exists:
            old_sub_int = query.get_subbed_ojs(channel.id)
            old_sub_bin = '{0:b}'.format(old_sub_int).zfill(
                len(self.onlineJudges.contest_judges))
            sub_bin = old_sub_bin
        selected = 'the selected '
        if len(ojs) == 0:
            sub_bin = '1' * len(self.onlineJudges.contest_judges)
            selected = 'all '
        sub_bin_mutable = list(sub_bin)
        for oj in ojs:
            sub_bin_mutable[self.onlineJudges.contest_judges.index(oj)] = '1'
        sub_bin = ''.join(sub_bin_mutable)
        sub_int = int(sub_bin, 2)

        if exists and sub_int == old_sub_int:
            await ctx.send(
                ctx.message.author.display_name +
                ', %s channel is already subscribed to %scontest notifications.'
                % (determiner, selected))
            return
        if not exists:
            query.sub_channel(channel.id)
        query.update_subbed_ojs(channel.id, sub_int)
        await ctx.send(ctx.message.author.display_name + ', ' +
                       channel.mention +
                       ' subscribed to %scontest notifications.' % selected)

    @commands.command()
    @commands.guild_only()
    async def subs(self, ctx):
        clist = ctx.message.author.display_name + ', Contest notification channels in this server:\n'
        for text_channel in ctx.message.guild.text_channels:
            if query.exists('subscriptions_contests', 'channel_id',
                            text_channel.id):
                sub_bin = '{0:b}'.format(query.get_subbed_ojs(
                    text_channel.id)).zfill(
                        len(self.onlineJudges.contest_judges))
                ojs = []
                for i, b in enumerate(sub_bin):
                    if b == '1':
                        ojs.append(self.onlineJudges.contest_judges[i])
                clist += text_channel.mention + ' `' + ', '.join(ojs) + '`\n'
        if clist == ctx.message.author.display_name + ', Contest notification channels in this server:\n':
            await ctx.send(
                ctx.message.author.display_name +
                ', There are no channels subscribed to contest notifications in this server :slight_frown:'
            )
        else:
            await ctx.send(clist)

    @commands.command()
    @commands.has_permissions(manage_channels=True)
    @commands.guild_only()
    async def unsub(self, ctx, channel: discord.TextChannel = None, *args):

        determiner = 'That'
        if channel is None:
            channel = ctx.message.channel
            determiner = 'This'

        exists = query.exists('subscriptions_contests', 'channel_id',
                              channel.id)

        if not exists:
            await ctx.send(
                ctx.message.author.display_name +
                ', %s channel is already not subscribed to contest notifications.'
                % determiner)
            return

        old_sub_int = None
        old_sub_bin = None

        ojs = []
        for arg in args:
            try:
                oj = self.onlineJudges.get_oj(arg)
                if oj not in self.onlineJudges.contest_judges:
                    raise NoSuchOJException
                ojs.append(oj)
            except NoSuchOJException:
                await ctx.send(
                    ctx.message.author.display_name +
                    ', Invalid query. The online judges must be one of the following: %s.'
                    % self.onlineJudges.contest_judges_str())
                return

        sub_bin = '1' * len(self.onlineJudges.contest_judges)
        if exists:
            old_sub_int = query.get_subbed_ojs(channel.id)
            old_sub_bin = '{0:b}'.format(old_sub_int).zfill(
                len(self.onlineJudges.contest_judges))
            sub_bin = old_sub_bin
        if len(ojs) == 0:
            sub_bin = '0' * len(self.onlineJudges.contest_judges)
        sub_bin_mutable = list(sub_bin)
        for oj in ojs:
            sub_bin_mutable[self.onlineJudges.contest_judges.index(oj)] = '0'
        sub_bin = ''.join(sub_bin_mutable)
        sub_int = int(sub_bin, 2)

        if sub_int == old_sub_int:
            await ctx.send(
                ctx.message.author.display_name +
                ', %s channel is already not subscribed to the selected contest notifications.'
                % determiner)
            return

        if sub_int == 0:
            query.unsub_channel(channel.id)
            await ctx.send(ctx.message.author.display_name + ', ' +
                           channel.mention +
                           ' is no longer a contest notification channel.')
        else:
            query.update_subbed_ojs(channel.id, sub_int)
            await ctx.send(
                ctx.message.author.display_name + ', ' + channel.mention +
                ' has been unsubscribed from contest notifications from the selected online judges'
            )

    def is_upcoming(self, contest):
        if '+' in contest.asdict()['Start Time']:
            return datetime.strptime(contest.asdict()['Start Time'],
                                     '%Y-%m-%d %H:%M:%S%z') > datetime.now(
                                         pytz.UTC)
        return datetime.strptime(contest.asdict()['Start Time'],
                                 '%Y-%m-%d %H:%M:%S') > datetime.now()

    def is_recent(self, contest):
        if '+' in contest.asdict()['Start Time']:
            return datetime.strptime(contest.asdict()['Start Time'],
                                     '%Y-%m-%d %H:%M:%S%z') > datetime.now(
                                         pytz.UTC) - timedelta(days=7)
        return datetime.strptime(
            contest.asdict()['Start Time'],
            '%Y-%m-%d %H:%M:%S') > datetime.now() - timedelta(days=7)

    @tasks.loop(minutes=7)
    async def refresh_contests(self):
        try:
            self.reset_contest('dmoj')
            self.parse_dmoj_contests()
        except:
            pass

        try:
            self.reset_contest('codeforces')
            self.parse_cf_contests()
        except:
            pass

        try:
            self.reset_contest('atcoder')
            self.parse_atcoder_contests()
        except:
            pass

        try:
            self.reset_contest('leetcode')
            self.reset_contest('codechef')
            self.reset_contest('topcoder')
            self.parse_external_contest_api()
        except:
            pass

        self.set_time()
        self.generate_stream()

        new_contests = list(
            set(self.contest_objects).difference(set(self.contest_cache)))

        for channel_id in query.get_all_subs():
            sub_bin = '{0:b}'.format(query.get_subbed_ojs(channel_id)).zfill(
                len(self.onlineJudges.contest_judges))
            channel_subbed = []
            for new_contest in new_contests:
                if sub_bin[self.onlineJudges.contest_judges.index(
                        new_contest.asdict()['oj'])] == '1':
                    channel_subbed.append(new_contest)
            try:
                channel = self.bot.get_channel(channel_id)
                await channel.send(embed=self.embed_multiple_contests(
                    channel_subbed, new=True))
            except:
                pass

        self.contest_cache = list(
            filter(
                self.is_recent,
                list(set(self.contest_objects).union(set(
                    self.contest_cache)))))
        self.update_contest_cache()

    @refresh_contests.before_loop
    async def check_contests_before(self):
        await self.bot.wait_until_ready()