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

Add bulk pairing client (#6) and rework account client #43

Merged
merged 9 commits into from
Sep 2, 2023
4 changes: 3 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ v0.12.8 (2023-07-25)
--------------------

* Added ``client.opening_explorer.get_lichess_games`` to request opening explorer data for a given position based on Lichess games
* Added ``client.bulk_pairings`` to create and manage bulk pairings
* Reworked ``client.account`` with better typing

Thanks to @rpesche for their contributions to this release.
Thanks to @rpesche and @Virinas-code for their contributions to this release.
benediktwerner marked this conversation as resolved.
Show resolved Hide resolved

v0.12.7 (2023-07-15)
--------------------
Expand Down
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ Most of the API is available:
client.broadcasts.get_round_pgns
client.broadcasts.get_pgns

client.bulk_pairings.view_upcoming
client.bulk_pairings.create
client.bulk_pairings.start_clocks
client.bulk_pairings.cancel

client.challenges.create
client.challenges.create_ai
client.challenges.create_open
Expand Down
4 changes: 4 additions & 0 deletions berserk/clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .tv import TV
from .tablebase import Tablebase
from .opening_explorer import OpeningExplorer
from .bulk_pairings import BulkPairings

__all__ = [
"Client",
Expand All @@ -41,6 +42,7 @@
"OAuth",
"TV",
"Tablebase",
"BulkPairings",
]


Expand All @@ -64,6 +66,7 @@ class Client(BaseClient):
- :class:`messaging <berserk.clients.Messaging>` - private message other players
- :class:`tv <berserk.clients.TV>` - get information on tv channels and games
- :class:`tablebase <berserk.clients.Tablebase>` - lookup endgame tablebase
- :class:`bulk_pairings <berserk.clients.BulkPairing>` - manage bulk pairings

:param session: request session, authenticated as needed
:param base_url: base API URL to use (if other than the default)
Expand Down Expand Up @@ -102,3 +105,4 @@ def __init__(
self.tv = TV(session, base_url)
self.tablebase = Tablebase(session, tablebase_url)
self.opening_explorer = OpeningExplorer(session, explorer_url)
self.bulk_pairings: BulkPairings = BulkPairings(session, base_url)
23 changes: 17 additions & 6 deletions berserk/clients/account.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
"""Account client."""
from __future__ import annotations

from typing import Any, Dict
from deprecated.sphinx import deprecated, versionchanged
from typing_extensions import cast

from .. import models
from ..types.account import AccountInformation, Preferences
from .base import BaseClient


class Account(BaseClient):
"""Client for account-related endpoints."""

def get(self) -> Dict[str, Any]:
def get(self) -> AccountInformation:
"""Get your public information.

:return: public information about the authenticated user
"""
path = "/api/account"
return self._r.get(path, converter=models.Account.convert)
return cast(
AccountInformation, self._r.get(path, converter=models.Account.convert)
)

def get_email(self) -> str:
"""Get your email address.
Expand All @@ -25,13 +30,18 @@ def get_email(self) -> str:
path = "/api/account/email"
return self._r.get(path)["email"]

def get_preferences(self) -> Dict[str, Any]:
@versionchanged(
":py:meth:`berserk.clients.Account.get_preferences` now returns "
"all the preferences including language",
version="0.12.8",
)
def get_preferences(self) -> Preferences:
"""Get your account preferences.

:return: preferences of the authenticated user
"""
path = "/api/account/preferences"
return self._r.get(path)["prefs"]
return cast(Preferences, self._r.get(path))

def get_kid_mode(self) -> bool:
"""Get your kid mode status.
Expand All @@ -41,7 +51,7 @@ def get_kid_mode(self) -> bool:
path = "/api/account/kid"
return self._r.get(path)["kid"]

def set_kid_mode(self, value: bool):
def set_kid_mode(self, value: bool) -> None:
"""Set your kid mode status.

:param bool value: whether to enable or disable kid mode
Expand All @@ -50,6 +60,7 @@ def set_kid_mode(self, value: bool):
params = {"v": value}
self._r.post(path, params=params)

@deprecated("Use endpoint from the Bot client instead", version="0.12.8")
benediktwerner marked this conversation as resolved.
Show resolved Hide resolved
def upgrade_to_bot(self):
"""Upgrade your account to a bot account.

Expand Down
91 changes: 91 additions & 0 deletions berserk/clients/bulk_pairings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Bulk pairings client"""
from __future__ import annotations

from typing import Any, cast, Dict, List, Optional

from .base import BaseClient
from ..enums import Variant
from ..formats import JSON_LIST, JSON
from ..types.bulk_pairings import BulkPairingGame
from ..types.core.aliases import ID


class BulkPairings(BaseClient):
"""Client for bluk pairing related endpoints."""

def view_upcoming(self) -> List[BulkPairingGame]:
"""View upcoming bulk pairings.

:return: List of upcoming bulk pairings.
:rtype: List[BulkPairingGame]
"""
path: str = "/api/bulk-pairing"
return cast(List[BulkPairingGame], self._r.get(path, fmt=JSON_LIST))

def create(
self,
players_tokens: List[str],
clock_limit: int,
clock_increment: int,
days: Optional[int] = None,
pair_at: Optional[int] = None,
start_clocks_at: Optional[int] = None,
rated: Optional[bool] = None,
variant: Optional[Variant] = None,
fen: Optional[str] = None,
message: Optional[str] = None,
rules: Optional[list[str]] = None,
benediktwerner marked this conversation as resolved.
Show resolved Hide resolved
) -> BulkPairingGame:
"""Create a bulk pairing.

:param List[str] players_tokens: players OAuth tokens
:param int clock_limit: clock initial time
:param int clock_increment: clock increment
:param Optional[int] days: days per turn (correspondence)
:param Optional[int] pair_at: day at wich game will be created
:param Optional[int] start_clocks_at: day at wich clocks will start
:param Optional[bool] rated: rated or casual
:param Optional[Variant] variant: game variant
:param Optional[str] fen: starting fen
:param Optional[str] message: message sent to players
:param Optional[list[str]] rules: extra game rules
:return BulkPairingGame: the pairing created
"""
path: str = "/api/bulk-pairing"
payload: Dict[str, Any] = {
benediktwerner marked this conversation as resolved.
Show resolved Hide resolved
"players": ":".join(players_tokens),
"clock.limit": clock_limit,
"clock.increment": clock_increment,
"days": days,
"pairAt": pair_at,
"startClocksAt": start_clocks_at,
"rated": rated,
"variant": variant,
"fen": fen,
"message": message,
"rules": ",".join(rules) if rules else None,
}
return cast(
BulkPairingGame,
self._r.post(
path,
payload=payload,
fmt=JSON,
),
)

def start_clocks(self, pairing_id: ID) -> None:
"""Manually start clocks.

:param LichessID pairing_id: pairing to start clocks of
"""
path: str = f"https://lichess.org/api/bulk-pairing/{pairing_id}/start-clocks"
self._r.post(path)

def cancel(self, pairing_id: ID) -> None:
"""Cancel a bulk pairing.

:param LichessID pairing_id: pairing to cancel
"""
path: str = f"https://lichess.org/api/bulk-pairing/{pairing_id}"
self._r.request("DELETE", path)
37 changes: 37 additions & 0 deletions berserk/enums.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""Common enumerations"""
from typing_extensions import Final

__all__ = ["PerfType", "Variant", "Color", "Room", "Mode", "Position", "Reason"]


Expand All @@ -20,6 +23,15 @@ class PerfType(GameType):
ULTRA_BULLET = "ultraBullet"


class AllPerfType(PerfType):
"""Perfs including puzzles, etc"""

STORM: str = "storm"
RACER: str = "racer"
STREAK: str = "streak"
PUZZLE: str = "puzzle"


class Variant(GameType):
STANDARD = "standard"

Expand Down Expand Up @@ -53,6 +65,31 @@ class Reason:
ONLYBOT = "onlyBot"


class CountTypes:
"""Types of games."""

ALL: Final[str] = "all"
RATED: Final[str] = "rated"
AI: Final[str] = "ai"
DRAW: Final[str] = "draw"
DRAW_H: Final[str] = "drawH"
LOSS: Final[str] = "loss"
LOSS_H: Final[str] = "lossH"
WIN: Final[str] = "win"
WIN_H: Final[str] = "winH"
BOOKMARK: Final[str] = "bookmark"
PLAYING: Final[str] = "playing"
IMPORT: Final[str] = "import"
ME: Final[str] = "me"


class StreamingService:
"""Streaming services."""

YOUTUBE: Final[str] = "youTube"
TWITCH: Final[str] = "twitch"


# fmt: off
class Position:
ALEKHINES_DEFENCE = 'rnbqkb1r/pppppppp/5n2/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 2 2' # noqa: E501
Expand Down
117 changes: 117 additions & 0 deletions berserk/types/account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""Aliases for account endpoints."""
from __future__ import annotations

from datetime import datetime

from typing_extensions import TypedDict

from ..enums import AllPerfType, CountTypes, StreamingService


class Perf(TypedDict):
"""A perf."""

games: int
rating: int
rd: int
prog: int
prov: bool


class PlayTime(TypedDict):
benediktwerner marked this conversation as resolved.
Show resolved Hide resolved
"""Account play time."""

total: int
tv: int


class Profile(TypedDict):
"""Public profile of an account."""

country: str
location: str
bio: str
firstName: str
lastName: str
fideRating: int
uscfRating: int
ecfRating: int
links: str


class StreamerInfo(TypedDict):
"""Information about the streamer on a given platform."""

channel: str
benediktwerner marked this conversation as resolved.
Show resolved Hide resolved


class AccountInformation(TypedDict):
"""Informations about an account."""

id: str
username: str
perfs: dict[AllPerfType, Perf]
createdAt: datetime
disabled: bool
tosViolation: bool
profile: Profile
seenAt: datetime
patron: bool
verified: bool
title: str
url: str
playing: str
count: dict[CountTypes, int]
streaming: bool
streamer: dict[StreamingService, StreamingService]
followable: bool
following: bool
blocking: bool
followsYou: bool


class Prefs(TypedDict):
"""User settings."""

dark: bool
transp: bool
bgImg: str
is3d: bool
theme: str
pieceSet: str
theme3d: str
pieceSet3d: str
soundSet: str
blindfold: int
autoQueen: int
autoThreefold: int
takeback: int
moretime: int
clockTenths: int
clockBar: bool
clockSound: bool
premove: bool
animation: int
captured: bool
follow: bool
highlight: bool
destination: bool
coords: int
replay: int
challenge: int
message: int
coordColor: int
submitMove: int
confirmResign: int
insightShare: int
keyboardMove: int
zen: int
moveEvent: int
rookCastle: int


class Preferences(TypedDict):
"""Preferences of an account."""

prefs: Prefs
language: str
Loading