Quick start
import asyncio
import crowdypy
API = "https://ck.prod.crowdedkingdoms.com"
async def main() -> None:
# The identity client: sign-in, account and studio calls, minting.
async with crowdypy.AsyncCrowdyClient(http_url=API) as identity:
await identity.auth.login("player@example.com", "correct-horse-battery")
if not await identity.auth.player_legal_acceptance():
# Only after the player ticked the box: this records their agreement.
await identity.auth.record_player_consents(accept_legal=True)
minted = await identity.portal.mint_app_token("42")
# The game client: one per app, at the app's own datacenter.
async with crowdypy.AsyncCrowdyClient(
http_url=minted.game_api_url or API, discovery_url=minted.discovery_url
) as game:
game.set_app_token(minted)
await game.udp.connect(minted) # assign a server, open the UDP socket
print(await game.users.me())
asyncio.run(main())
Without an event loop, crowdypy.sync.CrowdyClient has the same methods
without await.
Two tokens, two clients
-
Signing in yields an identity session token, for account, studio administration and minting. It is not accepted for gameplay.
-
Gameplay needs a short-lived app token per app (
portal.mint_app_token(app_id)), which is also the HMAC key for native UDP.client.refresh_gameplay_token()refreshes it, and concurrent callers share one refresh. A live UDP connection keeps its replication server across the refresh.The age and the terms (CrowdyPy 0.6.0 and 0.23.0). No app token is issued until the player has an age and has agreed to the current required legal documents, the Player Terms and the Player Privacy Policy; until then minting raises
AGE_REQUIREDorLEGAL_ACCEPTANCE_REQUIRED(crowdypy.is_legal_acceptance_required_error). Show your own checkboxes, linking each document (auth.player_legal_documents()and the app's ownauth.app_required_documents(app_id)), then callauth.record_player_consents(accept_legal=True, app_id=...).auth.register(..., accept_legal=True, birth_date=..., country=..., parental_confirmation=...)creates an account that starts accepted, with the answers of a neutral age screen; an account with no age gives it once withauth.record_player_age(...). See Terms, age and country. -
Build one identity client and one client per game. The game client points at the app's datacenter (
game_api_url), anddiscovery_urllets it find the app again if that instance stops answering.
Persist sessions with crowdypy.FileTokenStore:
FileTokenStore.session_path(directory, api_origin) names the identity session
file, and FileTokenStore.app_path(directory, app_id) an app token's.
CrowdyPy sends no Origin header, so direct sign-in (auth.login,
auth.register) is available to it. A game running in a browser on its own
domain signs players in through the hosted /authorize page instead, using
CrowdyJS.
Errors
Every error is a crowdypy.CrowdyError. A GraphQL refusal is a
CrowdyGraphQLError carrying the server's stable code
(error.code == "FORBIDDEN") and its extensions. HTTP, network and timeout
failures have their own classes. A WRONG_DATACENTER refusal moves the client
to the app's datacenter and retries once, without your code doing anything.
Some refusals carry more (0.8.0): access_refusal_of(error) reads ACCESS_REVOKED,
ACCESS_SUSPENDED (with when it lifts) and ACCESS_NOT_GRANTED from a mint;
actor_exists_of(error) reads ACTOR_EXISTS and whether the actor is already yours; and
app_paused_of(error) reads APP_PAUSED and its reason. A paused app still mints:
check is_app_paused(token.runtime_gate) and tell the player the world is paused.
From 0.11.0, account_blocked_of(error) reads ACCOUNT_BLOCKED and when a timed block
lifts, and group_member_banned_of(error) reads GROUP_MEMBER_BANNED from joining a
channel or team you are banned from. For org-managed players:
- From 0.16.0,
assertion_rejected_of(error)readsASSERTION_REJECTEDand the failed check, andmanaged_player_not_allowed_of(error)readsMANAGED_PLAYER_NOT_ALLOWED. - From 0.17.0,
org_legal_acceptance_required_of(error)readsORG_LEGAL_ACCEPTANCE_REQUIRED: the player's organization has not accepted the Org-Managed Players Addendum, which only an admin of the organization can fix, in Studio.
A ck-exec connection that the server closes with
4403 ("access ended") does not reconnect: pending calls fail Denied, and the
listener you gave on_access_ended hears the reason.
From 0.23.0, the player gate's refusals have readers too, in crowdypy.errors:
legal_acceptance_required_of (with missing_documents), age_required_of,
age_requirement_not_met_of (a boolean: it surfaces no age, and a game shows none),
territory_not_supported_of (with country), safety_log_required_of,
parental_consent_unavailable_of and social_limit_reached_of. From 0.24.0,
safety_moderator_required_of reads SAFETY_MODERATOR_REQUIRED, and
parental_consent_unavailable_of returns the refusal with its reason (not_configured,
provider_refused or provider_unavailable) instead of a boolean; apps.app() reads
hasSafetyModerator, and platform.territory_policies() rows carry regionCode.
Player safety
CrowdyPy 0.23.0 and 0.24.0 wrap the teen safety program. With the player's app token, client.safety
holds their own controls in the app: comm_settings, set_comm_settings,
request_parental_consent, contacts, contact_requests, request_contact, accept_contact,
remove_contact, blocks, block, unblock and file_report. apps.recording(app_id) says
what the safety log records, for the notice and the indicator. Moderators use client.moderation
(reports, resolve_report, escalate_report) with their identity session. See
Voice, video, contacts and blocks and
Safety reports and moderation.