Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement api/challenge endpoints #52

Merged
merged 18 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Anupya marked this conversation as resolved.
Show resolved Hide resolved
"""
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