Skip to content

Commit

Permalink
Implement api/challenge endpoints (#52)
Browse files Browse the repository at this point in the history
* Update berserk/clients/teams.py

Co-authored-by: Benedikt Werner <1benediktwerner@gmail.com>

* Update README.rst

* Update teams.py

* add challenge endpoints

* fix black formatting error

* list of any

* ok now all challenge endpoints have been implemented

* add TypedDicts and TypedAliases

* fix black and pyright errors

* use pre-defined constants in common

* move all literals into common

* fix issues

* black reformatting

* add to changelog

* refactor types

* Use List of usernames for `create_tokens_for_multiple_users`

more pythonic

---------

Co-authored-by: Benedikt Werner <1benediktwerner@gmail.com>
Co-authored-by: kraktus <kraktus@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 1, 2023
1 parent dd0df4c commit dba4fe9
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 42 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ Changelog
To be released
--------------

* Add::

client.challenges.get_mine
client.challenges.cancel
client.challenges.start_clocks
client.challenges.add_time_to_opponent_clock
client.challenges.create_tokens_for_multiple_users

* Add ``client.explorer.get_otb_master_game``
* Add::

Expand Down
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,17 @@ Most of the API is available:
client.bulk_pairings.start_clocks
client.bulk_pairings.cancel
client.challenges.get_mine
client.challenges.create
client.challenges.create_ai
client.challenges.create_open
client.challenges.create_with_accept
client.challenges.accept
client.challenges.decline
client.challenges.cancel
client.challenges.start_clocks
client.challenges.add_time_to_opponent_clock
client.challenges.create_tokens_for_multiple_users
client.explorer.get_lichess_games
client.explorer.get_masters_games
Expand Down
2 changes: 1 addition & 1 deletion berserk/clients/bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

from .. import models
from ..formats import NDJSON
from ..types.common import ChallengeDeclineReason
from .base import BaseClient
from ..types.challenges import ChallengeDeclineReason


class Bots(BaseClient):
Expand Down
73 changes: 71 additions & 2 deletions berserk/clients/challenges.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
from __future__ import annotations

from typing import Any, Dict
from typing import Any, Dict, List
from deprecated import deprecated

from ..types.common import ChallengeDeclineReason, Color, Variant
from ..types.challenges import Challenge, ChallengeDeclineReason
from ..types.common import Color, Variant
from .base import BaseClient


class Challenges(BaseClient):
def get_mine(self) -> Dict[str, List[Challenge]]:
"""Get all outgoing challenges (created by me) and incoming challenges (targeted at me).
Requires OAuth2 authorization with challenge:read scope.
:return: all my outgoing and incoming challenges
"""
path = "/api/challenge"
return self._r.get(path)

def create(
self,
username: str,
Expand Down Expand Up @@ -173,3 +184,61 @@ def decline(
path = f"/api/challenge/{challenge_id}/decline"
payload = {"reason": reason}
self._r.post(path, json=payload)

def cancel(self, challenge_id: str, opponent_token: str | None = None) -> None:
"""Cancel an outgoing challenge, or abort the game if challenge was accepted but the game was not yet played.
Requires OAuth2 authorization with challenge:write, bot:play and board:play scopes.
:param challenge_id: ID of a challenge
:param opponent_token: if set to the challenge:write token of the opponent, allows game to be cancelled
even if both players have moved
"""
path = f"/api/challenge/{challenge_id}/cancel"
params = {"opponentToken": opponent_token}
self._r.post(path=path, params=params)

def start_clocks(
self, game_id: str, token_player_1: str, token_player_2: str
) -> None:
"""Starts the clocks of a game immediately, even if a player has not yet made a move.
Requires the OAuth tokens of both players with challenge:write scope. The tokens can be in any order.
If the clocks have already started, the call will have no effect.
:param game_id: game ID
:param token_player_1: OAuth token of player 1 with challenge:write scope
:param token_player_2: OAuth token of player 2 with challenge:write scope
"""
path = f"/api/challenge/{game_id}/start-clocks"
params = {"token1": token_player_1, "token2": token_player_2}
self._r.post(path=path, params=params)

def add_time_to_opponent_clock(self, game_id: str, seconds: int) -> None:
"""Add seconds to the opponent's clock. Can be used to create games with time odds.
Requires OAuth2 authorization with challenge:write scope.
:param game_id: game ID
:param seconds: number of seconds to add to opponent's clock
"""
path = f"/api/round/{game_id}/add-time/{seconds}"
self._r.post(path)

def create_tokens_for_multiple_users(
self, usernames: List[str], description: str
) -> Dict[str, str]:
"""This endpoint can only be used by Lichess admins.
Create and obtain challenge:write tokens for multiple users.
If a similar token already exists for a user, it is reused. This endpoint is idempotent.
:param usernames: List of usernames
:param description: user-visible token description
:return: challenge:write tokens of each user
"""
path = "/api/token/admin-challenge"
payload = {"users": ",".join(usernames), "description": description}
return self._r.post(path=path, payload=payload)
8 changes: 4 additions & 4 deletions berserk/clients/opening_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .base import BaseClient
from ..types import (
OpeningStatistic,
OpeningExplorerVariant,
Variant,
Speed,
OpeningExplorerRating,
)
Expand All @@ -27,7 +27,7 @@ def __init__(self, session: requests.Session, explorer_url: str | None = None):

def get_lichess_games(
self,
variant: OpeningExplorerVariant = "standard",
variant: Variant = "standard",
position: str | None = None,
play: list[str] | None = None,
speeds: list[Speed] | None = None,
Expand Down Expand Up @@ -97,7 +97,7 @@ def get_player_games(
self,
player: str,
color: Color,
variant: OpeningExplorerVariant | None = None,
variant: Variant | None = None,
position: str | None = None,
play: list[str] | None = None,
speeds: list[Speed] | None = None,
Expand Down Expand Up @@ -145,7 +145,7 @@ def stream_player_games(
self,
player: str,
color: Color,
variant: OpeningExplorerVariant | None = None,
variant: Variant | None = None,
position: str | None = None,
play: list[str] | None = None,
speeds: list[Speed] | None = None,
Expand Down
7 changes: 4 additions & 3 deletions berserk/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

from .account import AccountInformation, Perf, Preferences, Profile, StreamerInfo
from .bulk_pairings import BulkPairing, BulkPairingGame
from .common import ClockConfig, ExternalEngine, LightUser, OnlineLightUser
from .challenges import Challenge
from .common import ClockConfig, ExternalEngine, LightUser, OnlineLightUser, Variant
from .puzzles import PuzzleRace
from .opening_explorer import (
OpeningExplorerRating,
OpeningExplorerVariant,
OpeningStatistic,
Speed,
)
Expand All @@ -18,13 +18,14 @@
"ArenaResult",
"BulkPairing",
"BulkPairingGame",
"Challenge",
"ClockConfig",
"ExternalEngine",
"LightUser",
"OnlineLightUser",
"CurrentTournaments",
"OpeningExplorerRating",
"OpeningExplorerVariant",
"Variant",
"OpeningStatistic",
"PaginatedTeams",
"Perf",
Expand Down
57 changes: 57 additions & 0 deletions berserk/types/challenges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from __future__ import annotations

from typing_extensions import TypeAlias, TypedDict, NotRequired, Literal

from .common import Variant, Color, OnlineLightUser
from .opening_explorer import Speed

ChallengeStatus: TypeAlias = Literal[
"created",
"offline",
"canceled",
"declined",
"accepted",
]

ChallengeDeclineReason: TypeAlias = Literal[
"generic",
"later",
"tooFast",
"tooSlow",
"timeControl",
"rated",
"casual",
"standard",
"variant",
"noBot",
"onlyBot",
]

ChallengeDirection: TypeAlias = Literal["in", "out"]


class User(OnlineLightUser):
"""Challenge User"""

rating: NotRequired[float]
provisional: NotRequired[bool]


class Challenge(TypedDict):
"""Information about a challenge."""

id: str
url: str
status: ChallengeStatus
challenger: User
destUser: User | None
variant: Variant
rated: bool
speed: Speed
timeControl: object
color: Color
perf: str
direction: NotRequired[ChallengeDirection]
initialFen: NotRequired[str]
declineReason: str
declineReasonKey: str
21 changes: 6 additions & 15 deletions berserk/types/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import Union, Literal
from typing import Union

from typing_extensions import Literal, TypedDict, TypeAlias, NotRequired

Expand Down Expand Up @@ -47,6 +47,11 @@ class ExternalEngine(TypedDict):
"fromPosition",
]

Speed = Literal[
"ultraBullet", "bullet", "blitz", "rapid", "classical", "correspondence"
]


Title = Literal[
"GM", "WGM", "IM", "WIM", "FM", "WFM", "NM", "CM", "WCM", "WNM", "LM", "BOT"
]
Expand Down Expand Up @@ -77,17 +82,3 @@ class OnlineLightUser(LightUser):
GameRule: TypeAlias = Literal[
"noAbort", "noRematch", "noGiveTime", "noClaimWin", "noEarlyDraw"
]

ChallengeDeclineReason: TypeAlias = Literal[
"generic",
"later",
"tooFast",
"tooSlow",
"timeControl",
"rated",
"casual",
"standard",
"variant",
"noBot",
"onlyBot",
]
18 changes: 1 addition & 17 deletions berserk/types/opening_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,7 @@

from typing import Literal, List
from typing_extensions import TypedDict

OpeningExplorerVariant = Literal[
"standard",
"chess960",
"crazyhouse",
"antichess",
"atomic",
"horde",
"kingOfTheHill",
"racingKings",
"threeCheck",
"fromPosition",
]

Speed = Literal[
"ultraBullet", "bullet", "blitz", "rapid", "classical", "correspondence"
]
from .common import Speed

OpeningExplorerRating = Literal[
"0", "1000", "1200", "1400", "1600", "1800", "2000", "2200", "2500"
Expand Down

0 comments on commit dba4fe9

Please sign in to comment.