sidequest_bot.api_client#
Typed HTTP client for the SideQuest API. The only way the bot talks to the API.
Attributes#
Exceptions#
The API returned an error response, or couldn't be reached. Every client failure is an APIError. |
|
The API answered, but not in the shape this bot expects: usually the other half of a rolling deploy. |
|
The API couldn't be reached (connection/timeout) after retries. Reported as status 503. |
|
The API returned 404. |
Classes#
Async client for the SideQuest API. |
Module Contents#
- exception sidequest_bot.api_client.APIError(status_code: int, detail: str)#
Bases:
ExceptionThe API returned an error response, or couldn’t be reached. Every client failure is an APIError.
Initialize self. See help(type(self)) for accurate signature.
- detail#
- status_code#
- exception sidequest_bot.api_client.APIResponseShapeError(status_code: int, detail: str)#
Bases:
APIErrorThe API answered, but not in the shape this bot expects: usually the other half of a rolling deploy.
Raised as an APIError (status 502) so every caller’s existing APIError handling covers it; a bare pydantic ValidationError escaping a discord.py
tasks.loopwould stop that loop until the bot restarts.Initialize self. See help(type(self)) for accurate signature.
Bases:
APIErrorThe API couldn’t be reached (connection/timeout) after retries. Reported as status 503.
Initialize self. See help(type(self)) for accurate signature.
- exception sidequest_bot.api_client.NotFoundError(status_code: int, detail: str)#
Bases:
APIErrorThe API returned 404.
Initialize self. See help(type(self)) for accurate signature.
- class sidequest_bot.api_client.SideQuestAPI(base_url: str, api_key: str, *, client: httpx.AsyncClient | None = None)#
Async client for the SideQuest API.
Retries 3x with backoff: idempotent methods on transport errors/5xx, POST only when the connection never opened (so a case or XP award is never recorded twice). 4xx is never retried.
Pass
clientto reuse an existinghttpx.AsyncClient(tests pass Litestar’sAsyncTestClient).- async ack_badge(guild_id: int, ack: sidequest_common.schemas.BadgeAck) None#
Confirm one member’s badge announcement posted, so
sync_badgesstops reporting it as pending.
- async ack_event_activity(guild_id: int, ids: list[uuid.UUID]) None#
Confirm these sign-up activity rows were posted in their event’s forum thread.
- async ack_event_push(guild_id: int, event_id: uuid.UUID, ack: sidequest_common.schemas.DiscordPushAck) None#
Confirm
ack.push_hashwas applied (or skipped) forevent_id.
- async ack_role_reset(guild_id: int, reset_id: uuid.UUID) None#
Confirm the bot stripped this member’s roles (or found them no longer in the guild).
- async ack_signup_notice(guild_id: int, member_id: int, ack: sidequest_common.schemas.SignupNoticeAck) None#
Confirm the bot’s message for
member_idnow reflects their current roster state.
- async aclose() None#
Close the underlying HTTP client if this instance created it.
- async award_xp(guild_id: int, user_id: int) sidequest_common.schemas.XPAwardResult#
Award one message’s worth of XP (API enforces cooldown and amount).
- async confirm_roster_from_notice(guild_id: int, member_id: int, request: sidequest_common.schemas.ConfirmByDiscordRequest) sidequest_common.schemas.MemberListing#
Confirm site member
member_idon the roster, with the acting GM identified by Discord id.
- async create_case(guild_id: int, case: sidequest_common.schemas.ModCaseCreate) sidequest_common.schemas.ModCaseSchema#
Record a moderation action; the API assigns the case number.
Create a menu (no message yet; PATCH
message_idafter posting).
- async delete_event_sync_tombstone(guild_id: int, event_id: int) None#
Clear the tombstone for
event_idafter deleting it in Discord.
- async delete_level_role(guild_id: int, level: int) None#
Remove the reward at
level. Raises NotFoundError if none.
Delete a menu and its options.
Remove the button for
role_id; returns the updated menu.
- async get_away_now(guild_id: int) list[int]#
Discord ids of members marked away today (America/Chicago).
- async get_case(guild_id: int, case_number: int) sidequest_common.schemas.ModCaseSchema#
One case. Raises NotFoundError if missing.
- async get_event_signup_by_discord(guild_id: int, event_id: uuid.UUID, discord_id: int) sidequest_common.schemas.SignupByDiscord#
A member’s current signup for
event_idplus their most recent character/class/role, by Discord id.Raises
NotFoundErrorifdiscord_idhas never signed in on the site, orevent_iddoesn’t exist.
- async get_guild_config(guild_id: int) sidequest_common.schemas.GuildConfigSchema#
Config for
guild_id(created with defaults on first access).
- async get_leaderboard(guild_id: int, *, limit: int = 10, offset: int = 0) sidequest_common.schemas.LeaderboardPage#
Highest XP first.
- async get_pending_event_sync(guild_id: int) sidequest_common.schemas.DiscordSyncPending#
Pushes and deletions the bot should apply to Discord now.
- async get_rank(guild_id: int, user_id: int) sidequest_common.schemas.RankSchema#
A member’s rank; members without XP get
rank=Noneand zeros.
- async get_recruiting() sidequest_common.schemas.Recruiting#
What roles and classes the guild is recruiting for (the public
/api/recruitingGET).
One menu with its options.
- async get_voice_presence(guild_id: int, event_id: uuid.UUID) sidequest_common.schemas.VoicePresenceReport#
Currently stored cumulative voice minutes per member for
event_id, to resume tracking after a restart.
- async list_cases(guild_id: int, *, user_id: int | None = None, limit: int = 25, offset: int = 0) list[sidequest_common.schemas.ModCaseSchema]#
Newest first, optionally only for
user_id.
- async list_level_roles(guild_id: int) list[sidequest_common.schemas.LevelRoleSchema]#
Level rewards, lowest level first.
- async list_member_links(guild_id: int) list[sidequest_common.schemas.MemberLink]#
Every signed-in member of
guild_id, and whether they’ve also linked Battle.net.
- async list_pending_announcements(guild_id: int) list[sidequest_common.schemas.PendingAnnouncement]#
Discord-queued announcements not yet posted, oldest first.
All menus in the guild.
- async list_role_resets(guild_id: int) list[sidequest_common.schemas.RoleResetCandidate]#
Pending Discord role resets owed after a site account deletion, oldest first, capped at 50.
- async list_signup_notices(guild_id: int) sidequest_common.schemas.SignupNoticesPage#
Members needing a new notice posted, and posted notices whose message is now stale.
- async list_upcoming_events() list[sidequest_common.schemas.EventSummary]#
Upcoming guild events, soonest first (the public
/api/eventslist; also materializes repeats).
- async list_weekly_board_events(guild_id: int, start: datetime.datetime, end: datetime.datetime) list[sidequest_common.schemas.WeeklyBoardEvent]#
Scheduled events starting in
[start, end), soonest first, with their forum thread id if any.
- async mark_announcement_posted(guild_id: int, announcement_id: uuid.UUID, message_id: int) None#
Record that
announcement_idposted to Discord asmessage_id.
- async put_guild_roles(guild_id: int, roles: list[sidequest_common.schemas.GuildRoleIn]) None#
Replace
guild_id’s full role set (excluding@everyone, dropped by the caller).
- async put_member_profiles(guild_id: int, profiles: list[sidequest_common.schemas.MemberProfileIn], departed_ids: list[int]) None#
Push linked members’ current Discord profiles and report linked members who’ve left.
- async reconcile_event_sync(guild_id: int, reconcile: sidequest_common.schemas.DiscordEventReconcile) None#
Report every scheduled event currently in the guild.
- async report_discord_event(guild_id: int, state: sidequest_common.schemas.DiscordEventState) None#
Report that Discord now holds
statefor one event (created, edited, or read off the gateway).
- async report_discord_event_deleted(guild_id: int, event_id: int) None#
Report that
event_idwas deleted in Discord.
- async report_discord_live(guild_id: int, report: sidequest_common.schemas.DiscordLiveReport) None#
Push the current voice/presence snapshot for
guild_id.
- async report_event_interest(guild_id: int, discord_event_id: int, report: sidequest_common.schemas.DiscordInterestReport) None#
Replace the interested-user set for the guild event linked to
discord_event_id; no-op if unlinked.
- async report_voice_presence(guild_id: int, report: sidequest_common.schemas.VoicePresenceReport) None#
Push cumulative voice minutes per member for one event so far.
Idempotent: the API keeps
max(stored, reported)per member, so a repeated or restart-reset push never lowers the stored total.
- async set_level_role(guild_id: int, level: int, role_id: int) sidequest_common.schemas.LevelRoleSchema#
Grant
role_idatlevel(replaces any existing reward for that level).
- async set_xp(guild_id: int, user_id: int, xp: int) sidequest_common.schemas.MemberLevelSchema#
Overwrite a member’s XP total; level is recomputed.
- async sync_badges(guild_id: int) sidequest_common.schemas.BadgeSyncReport#
Recompute every member’s badges, award newly earned ones, and report role state + new announcements.
- async unconfirm_roster_from_notice(guild_id: int, member_id: int, request: sidequest_common.schemas.ConfirmByDiscordRequest) sidequest_common.schemas.MemberListing#
Clear site member
member_id’s roster confirmation, with the acting GM identified by Discord id.
- async update_case(guild_id: int, case_number: int, changes: sidequest_common.schemas.ModCaseUpdate) sidequest_common.schemas.ModCaseSchema#
Edit a case (currently just its reason).
- async update_guild_config(guild_id: int, changes: sidequest_common.schemas.GuildConfigUpdate) sidequest_common.schemas.GuildConfigSchema#
Apply only the fields set on
changes.
Apply only the fields set on
changes.
- async upsert_event_signup_by_discord(guild_id: int, event_id: uuid.UUID, discord_id: int, data: sidequest_common.schemas.SignupUpsert) sidequest_common.schemas.Signup#
Create or replace a member’s signup for
event_id, by Discord id (Discord’s sign-up buttons).Raises
NotFoundErrorifdiscord_idhas never signed in. The caller is responsible for handling a 409 (event not scheduled) or 400 (role kind missing character/class/role)APIError.
Add or edit the button for
role_id; returns the updated menu.
- sidequest_bot.api_client.API_KEY_HEADER = 'X-API-Key'#
- sidequest_bot.api_client.logger#