ctfy.sdk.resources.achievements

client.achievements — public badge catalog + recent-unlock feed.

 1"""``client.achievements`` — public badge catalog + recent-unlock feed."""
 2
 3from __future__ import annotations
 4
 5from ctfy.sdk._helpers import _raise_for_status
 6from ctfy.sdk.base import BaseHttpClient
 7from ctfy.server.models import AchievementCatalogEntry, EasterEggClaim, RecentUnlock
 8
 9
10class AchievementsResource:
11    """The platform-wide achievement catalog, recent unlocks, easter eggs."""
12
13    def __init__(self, http: BaseHttpClient) -> None:
14        self._http = http
15
16    def catalog(self, offset: int = 0, limit: int = 50) -> list[AchievementCatalogEntry]:
17        """Every achievement defined on the platform (secret badges
18        hidden from non-admin viewers until earned)."""
19        resp = self._http.request(
20            "GET", "/achievements/catalog", params={"offset": offset, "limit": limit}
21        )
22        _raise_for_status(resp)
23        return [AchievementCatalogEntry.model_validate(a) for a in resp.json()["items"]]
24
25    def recent_unlocks(self, offset: int = 0, limit: int = 50) -> list[RecentUnlock]:
26        """Platform-wide feed of recent badge unlocks."""
27        resp = self._http.request(
28            "GET",
29            "/achievements/recent-unlocks",
30            params={"offset": offset, "limit": limit},
31        )
32        _raise_for_status(resp)
33        return [RecentUnlock.model_validate(r) for r in resp.json()["items"]]
34
35    def claim_easter_egg(self, egg_id: str, competition_id: str = "") -> EasterEggClaim:
36        """Grant the achievement bound to a discovered easter egg.
37
38        Idempotent: a second claim returns the same achievement with
39        ``already_unlocked=True``. ``competition_id`` is needed when the
40        caller is on multiple per-comp teams; single-team callers leave
41        it empty.
42        """
43        params = {"competition_id": competition_id} if competition_id else None
44        resp = self._http.request("POST", f"/easter-eggs/{egg_id}/claim", params=params)
45        _raise_for_status(resp)
46        return EasterEggClaim.model_validate(resp.json())
class AchievementsResource:
11class AchievementsResource:
12    """The platform-wide achievement catalog, recent unlocks, easter eggs."""
13
14    def __init__(self, http: BaseHttpClient) -> None:
15        self._http = http
16
17    def catalog(self, offset: int = 0, limit: int = 50) -> list[AchievementCatalogEntry]:
18        """Every achievement defined on the platform (secret badges
19        hidden from non-admin viewers until earned)."""
20        resp = self._http.request(
21            "GET", "/achievements/catalog", params={"offset": offset, "limit": limit}
22        )
23        _raise_for_status(resp)
24        return [AchievementCatalogEntry.model_validate(a) for a in resp.json()["items"]]
25
26    def recent_unlocks(self, offset: int = 0, limit: int = 50) -> list[RecentUnlock]:
27        """Platform-wide feed of recent badge unlocks."""
28        resp = self._http.request(
29            "GET",
30            "/achievements/recent-unlocks",
31            params={"offset": offset, "limit": limit},
32        )
33        _raise_for_status(resp)
34        return [RecentUnlock.model_validate(r) for r in resp.json()["items"]]
35
36    def claim_easter_egg(self, egg_id: str, competition_id: str = "") -> EasterEggClaim:
37        """Grant the achievement bound to a discovered easter egg.
38
39        Idempotent: a second claim returns the same achievement with
40        ``already_unlocked=True``. ``competition_id`` is needed when the
41        caller is on multiple per-comp teams; single-team callers leave
42        it empty.
43        """
44        params = {"competition_id": competition_id} if competition_id else None
45        resp = self._http.request("POST", f"/easter-eggs/{egg_id}/claim", params=params)
46        _raise_for_status(resp)
47        return EasterEggClaim.model_validate(resp.json())

The platform-wide achievement catalog, recent unlocks, easter eggs.

AchievementsResource(http: ctfy.sdk.base.BaseHttpClient)
14    def __init__(self, http: BaseHttpClient) -> None:
15        self._http = http
def catalog( self, offset: int = 0, limit: int = 50) -> list[ctfy.server.models.AchievementCatalogEntry]:
17    def catalog(self, offset: int = 0, limit: int = 50) -> list[AchievementCatalogEntry]:
18        """Every achievement defined on the platform (secret badges
19        hidden from non-admin viewers until earned)."""
20        resp = self._http.request(
21            "GET", "/achievements/catalog", params={"offset": offset, "limit": limit}
22        )
23        _raise_for_status(resp)
24        return [AchievementCatalogEntry.model_validate(a) for a in resp.json()["items"]]

Every achievement defined on the platform (secret badges hidden from non-admin viewers until earned).

def recent_unlocks( self, offset: int = 0, limit: int = 50) -> list[ctfy.server.models.RecentUnlock]:
26    def recent_unlocks(self, offset: int = 0, limit: int = 50) -> list[RecentUnlock]:
27        """Platform-wide feed of recent badge unlocks."""
28        resp = self._http.request(
29            "GET",
30            "/achievements/recent-unlocks",
31            params={"offset": offset, "limit": limit},
32        )
33        _raise_for_status(resp)
34        return [RecentUnlock.model_validate(r) for r in resp.json()["items"]]

Platform-wide feed of recent badge unlocks.

def claim_easter_egg( self, egg_id: str, competition_id: str = '') -> ctfy.server.models.EasterEggClaim:
36    def claim_easter_egg(self, egg_id: str, competition_id: str = "") -> EasterEggClaim:
37        """Grant the achievement bound to a discovered easter egg.
38
39        Idempotent: a second claim returns the same achievement with
40        ``already_unlocked=True``. ``competition_id`` is needed when the
41        caller is on multiple per-comp teams; single-team callers leave
42        it empty.
43        """
44        params = {"competition_id": competition_id} if competition_id else None
45        resp = self._http.request("POST", f"/easter-eggs/{egg_id}/claim", params=params)
46        _raise_for_status(resp)
47        return EasterEggClaim.model_validate(resp.json())

Grant the achievement bound to a discovered easter egg.

Idempotent: a second claim returns the same achievement with already_unlocked=True. competition_id is needed when the caller is on multiple per-comp teams; single-team callers leave it empty.