text: # Copyright 2019 LiveSite authors
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import argparse
  import re
  
  import bs4
  import requests
  
  from livecli.scrapers import base
  
  _DEFAULT_COLORS = (
      '#F44336',  # red
      '#4CAF50',  # green
      '#EAD61E',  # yellow
      '#3F51B5',  # blue
      '#F48FB1',  # pink
      '#FF9800',  # orange
      '#81D4FA',  # cyan
      '#E040FB',  # purple
      '#76FF03',  # light green
      '#FFFFFF',  # white
      '#000000',  # black
  )
  
  _LABEL_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  
  
  class DomjudgeScraper(base.Scraper):
      def __init__(self, options: argparse.Namespace):
          self._options = options
  
      def scrape_impl(self, html: str) -> dict:
          doc = bs4.BeautifulSoup(html, 'html5lib')
  
          if doc.select('#loginform'):
              raise base.NeedLoginException()
  
          standings = {'problems': [], 'entries': []}
  
          scoreheader_elems = doc.select('.scoreheader')
          if not scoreheader_elems:
              raise Exception('Scoreboard not available yet')
          scoreheader_elem = scoreheader_elems[0]
          for index_problem, problem_elem in enumerate(scoreheader_elem.select('th')[3:]):
              # label = problem_elem.get_text().strip()
              label = _LABEL_CHARS[index_problem % len(_LABEL_CHARS)]
              tooltip = problem_elem.attrs.get('title', '')
              if tooltip.startswith('problem '):
                  name = tooltip[len('problem '):].strip(' \'')
              else:
                  name = ''
              circles = problem_elem.select('.circle')
              if circles:
                  style = circles[0].attrs.get('style', '')
                  m = re.search(r'background:\s*([^\s;]+)\s*;', style)
                  color = m.group(1) if m else ''
              else:
                  color = _DEFAULT_COLORS[index_problem % len(_DEFAULT_COLORS)]
              standings['problems'].append({
                  'label': label,
                  # 'name': name,
                  'name': label,
                  'color': color,
              })
  
          scoreboard_elem = doc.select('table.scoreboard')[0]
          last_rank = 1
          for team_elem in scoreboard_elem.select('tbody tr'):
              if not team_elem.attrs.get('id', '').startswith('team:'):
                  break
              team_problems = []
              for problem_elem in team_elem.select('td.score_cell'):
                  text = problem_elem.get_text().strip()
                  if not text:
                      penalty, attempts, pendings = 0, 0, 0
                  else:
                      m = re.search(r'^(?:(?P<penalty>\d+)\s+)?(?P<attempts>\d+)\s+(?:\+\s+(?P<pendings>\d+)\s+)?(?:try|tries)$', text)
                      assert m, text
                      penalty = int(m.group('penalty') or '0')
                      attempts = int(m.group('attempts'))
                      pendings = int(m.group('pendings') or '0')
                  classes = problem_elem['class']
                  solved = len(problem_elem.select('.score_correct')) > 0
                  team_problems.append({
                      'attempts': attempts,
                      'pendings': pendings,
                      'penalty': penalty,
                      'solved': solved,
                  })
              rank = team_elem.select('.scorepl')[0].get_text().strip()
              if rank:
                  last_rank = rank = int(rank)
              else:
                  rank = last_rank
              name = team_elem.select('.scoretn .forceWidth')[0].get_text().strip()
              # Strip badge texts
              for badge in team_elem.select('.badge'):
                  prefix = badge.get_text().strip()
                  assert name.startswith(prefix)
                  name = name[len(prefix):].strip()
              try:
                  tid = str(int(name.split(':', 1)[0], 10))
              except ValueError:
                  continue
              solved = int(team_elem.select('.scorenc')[0].get_text().strip())
              penalty = int(team_elem.select('.scorett')[0].get_text().strip())
              standings['entries'].append({
                  'teamId': tid,
                  'rank': rank,
                  'solved': solved,
                  'penalty': penalty,
                  'problems': team_problems,
              })
          return standings
  
      def login(self, session: requests.Session) -> None:
          r = session.get(self._options.login_url)
          r.raise_for_status()
  
          doc = bs4.BeautifulSoup(r.text, 'html5lib')
          form = doc.select('#loginform form')[0]
  
          params = {}
          for kv in form.select('input[type=hidden]'):
              params[kv.attrs['name']] = kv.attrs['value']
          params['_username'] = self._options.login_user
          params['_password'] = self._options.login_password
  
          r = session.post(self._options.login_url, params)
          r.raise_for_status()
  
          doc = bs4.BeautifulSoup(r.text, 'html5lib')
          alerts = doc.select('#loginform .login-content .alert')
          if alerts:
              raise Exception('Login failed: %s' % alerts[0].get_text())
  