diff --git a/berserk/clients.py b/berserk/clients.py index f0b13fd..1856895 100644 --- a/berserk/clients.py +++ b/berserk/clients.py @@ -7,7 +7,6 @@ from .formats import JSON, LIJSON, PGN, NDJSON, TEXT from . import models - __all__ = [ 'Client', 'Account', @@ -23,7 +22,6 @@ 'Users', ] - # Base URL for the API API_URL = 'https://lichess.org/' @@ -576,7 +574,7 @@ def create_ai(self, level=8, clock_limit=None, clock_increment=None, :return: success indicator :rtype: bool """ - path = f'api/challenge/ai' + path = "api/challenge/ai" payload = { 'level': level, 'clock.limit': clock_limit, @@ -603,7 +601,7 @@ def create_open(self, clock_limit=None, clock_increment=None, :return: challenge data :rtype: dict """ - path = f'api/challenge/open' + path = "api/challenge/open" payload = { 'clock.limit': clock_limit, 'clock.increment': clock_increment, @@ -886,10 +884,12 @@ def get(self): path = 'api/tournament' return self._r.get(path, converter=models.Tournaments.convert_values) - def create(self, clock_time, clock_increment, minutes, name=None, - wait_minutes=None, variant=None, berserkable=None, rated=None, - start_date=None, position=None, password=None, conditions=None): - """Create a new tournament. + def create_arena(self, clock_time, clock_increment, minutes, name=None, + wait_minutes=None, start_date=None, variant=None, + rated=None, position=None, berserkable=None, + streakable=None, hasChat=None, description=None, + password=None, teambBattleByTeam=None, conditions=None): + """Create a new arena tournament. .. note:: @@ -900,7 +900,7 @@ def create(self, clock_time, clock_increment, minutes, name=None, If ``name`` is left blank then one is automatically created. - :param int clock_time: intial clock time in minutes + :param int clock_time: initial clock time in minutes :param int clock_increment: clock increment in seconds :param int minutes: length of the tournament in minutes :param str name: tournament name @@ -908,9 +908,16 @@ def create(self, clock_time, clock_increment, minutes, name=None, :param str start_date: when to start the tournament :param str variant: variant to use if other than standard :param bool rated: whether the game affects player ratings - :param str berserkable: whether players can use berserk :param str position: custom initial position in FEN - :param str password: password (makes the tournament private) + :param str berserkable: whether players can use berserk + :param bool streakable: whether players get streaks + :param bool hasChat: whether players can + discuss in a chat + :param string description: anything you want to + tell players about the tournament + :param str password: password + :param str teambBattleByTeam: Id of a team you lead + to create a team battle :param dict conditions: conditions for participation :return: created tournament info :rtype: dict @@ -927,15 +934,64 @@ def create(self, clock_time, clock_increment, minutes, name=None, 'rated': rated, 'position': position, 'berserkable': berserkable, + 'streakable': streakable, + 'hasChat': hasChat, + 'description': description, 'password': password, + 'teambBattleByTeam': teambBattleByTeam, **{f'conditions.{c}': v for c, v in (conditions or {}).items()}, } return self._r.post(path, json=payload, converter=models.Tournament.convert) - def export_games(self, id_, as_pgn=False, moves=None, tags=None, - clocks=None, evals=None, opening=None): - """Export games from a tournament. + def create_swiss(self, teamId_, clock_limit, clock_increment, nbRounds, + name=None, startsAt=None, roundInterval=None, + variant=None, description=None, rated=None, chatFor=None): + """Create a new swiss tournament + + .. note:: + + If ``name`` is left blank then one is automatically created. + + .. note:: + + If ``startsAt`` is left blank then the + tournament begins 10 minutes after creation + + :param string teamId_: team Id, required for swiss tournaments + :param int clock_limit: initial clock time in seconds + :param int clock_increment: clock increment in seconds + :param int nbRounds: maximum number of rounds to play + :param string name: tournament name + :param int startsAt: when to start tournament, in ms timestamp + :param int roundInterval: interval between rounds in seconds + :param string variant: variant to use if other than standard + :param string description: tournament description + :param bool rated: whether the game affects player ratings + :param int chatFor: who can read and write in the chat + :return: created tournament info + :rtype: dict + """ + path = f'api/swiss/new/{teamId_}' + + payload = { + 'name': name, + 'clock.limit': clock_limit, + 'clock.increment': clock_increment, + 'nbRounds': nbRounds, + 'startsAt': startsAt, + 'roundInterval': roundInterval, + 'variant': variant, + 'description': description, + 'rated': rated, + 'chatFor': chatFor + } + return self._r.post(path, json=payload, + converter=models.Tournament.convert) + + def export_arena_games(self, id_, as_pgn=False, moves=None, tags=None, + clocks=None, evals=None, opening=None): + """Export games from a arena tournament. :param str id_: tournament ID :param bool as_pgn: whether to return PGN instead of JSON @@ -961,6 +1017,88 @@ def export_games(self, id_, as_pgn=False, moves=None, tags=None, return self._r.get(path, params=params, fmt=fmt, converter=models.Game.convert) + def export_swiss_games(self, id_, as_pgn=False, moves=None, pgnInJson=None, + tags=None, clocks=None, evals=None, opening=None): + """Export games from a swiss tournament + + :param str id_: tournament id + :param bool as_pgn: whether to return pgn instead of JSON + :param bool moves: include moves + :param bool pgnInJson: include the full PGN within the + JSON response, in a pgn field + :param bool tags: include tags + :param bool clocks: include clock comments + :param bool evals: include analysis evaluation + comments in the PGN, when available + :param bool opening: include the opening name + :return: games + :rtype: list + """ + path = f'api/swiss/{id_}/games' + params = { + 'moves:': moves, + 'pgnInJson': pgnInJson, + 'tags': tags, + 'clocks': clocks, + 'evals': evals, + 'opening': opening, + } + fmt = PGN if self._use_pgn(as_pgn) else NDJSON + return self._r.get(path, params=params, fmt=fmt, + converter=models.Game.convert) + + def tournaments_by_user(self, username, nb=None, as_pgn=False): + """Get tournaments created by a user + + :param string username: username + :param int nb: max number of tournaments to fetch + :param bool as_pgn: whether to return pgn instead of Json + :return: tournaments + :rtype: list + """ + + path = f'api/user/{username}/tournament/created' + params = { + 'nb': nb, + } + fmt = PGN if self._use_pgn(as_pgn) else NDJSON + return self._r.get(path, params=params, fmt=fmt, + converter=models.Game.convert) + + def arenas_by_team(self, teamId, maxT=None, as_pgn=False): + """Get arenas created for a team + + :param string teamId: Id of the team + :param int maxT: how many tournaments to download + :param bool as_pgn: whether to return pgn instead of Json + :return: tournaments + :rtype: list + """ + path = f'api/team/{teamId}/arena' + params = { + 'max': maxT, + } + fmt = PGN if self._use_pgn(as_pgn) else NDJSON + return self._r.get(path, params=params, fmt=fmt, + converter=models.Game.convert) + + def swiss_by_team(self, teamId, maxT=None, as_pgn=False): + """Get swiss tournaments created for a team + + :param string teamId: Id of the team + :param int maxT: how many tournaments to download + :param bool as_pgn: whether to return pgn instead of Json + :return: tournaments + :rtype: list + """ + path = f'api/team/{teamId}/swiss' + params = { + 'max': maxT, + } + fmt = PGN if self._use_pgn(as_pgn) else NDJSON + return self._r.get(path, params=params, fmt=fmt, + converter=models.Game.convert) + def stream_results(self, id_, limit=None): """Stream the results of a tournament. diff --git a/docs/usage.rst b/docs/usage.rst index 3e64af7..d922416 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -399,7 +399,7 @@ tournament is easy: .. code-block:: python - >>> client.tournaments.create(clock_time=10, clock_increment=3, minutes=180) + >>> client.tournaments.create_arena(clock_time=10, clock_increment=3, minutes=180) {'berserkable': True, 'clock': {'increment': 3, 'limit': 600}, 'createdBy': 'rhgrant10', @@ -428,6 +428,31 @@ provided enum value in ``berserk.enums.Position``: >>> client.tournaments.create(clock_time=10, clock_increment=3, minutes=180, position=berserk.enums.Position.KINGS_PAWN) +You can also create Swiss tournaments easily, specifying the team id, clock time, +clock increment, and number of rounds. + +.. code-block:: python + + >>> client.tournaments.create_swiss(teamid_="coders", clock_limit=10, + clock_increment=0, nbRounds=5) + {'rated': true, + 'clock': {'increment': 0, 'limit': 600}, + 'createdBy': "zccze", + 'greatPlayer': {'name': "Wang', + 'url':'https://wikipedia.org/wiki/Wang_Hao_(chess_player)' }, + 'id': '3uwyXjiC' + 'name': 'Wang', + 'nbOngoing': 0, + 'nbPlayers': 0, + 'nbRounds': 5, + 'nextRound': { 'at': '2021-05-18T12:23:18.233-06:00', 'in': 600}, + 'quote': {'author': 'Bent Larsen', + 'text': 'I often play a move I know how to refute.'}, + 'round': 0, + 'startsAt': '2021-05-18T12:23:18.233-06:00', + 'status': 'created', + 'variant': 'standard' + } Additionally you can see tournaments that have recently finished, are in progress, and are about to start: