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.
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.