Xeonr Developer Docs

Notifications

XMB notifies people about what happens on their servers — a request being approved, a season arriving, a hub going offline — through four channels: the in-app inbox, push (native apps and browsers), email, and Discord.

Three services carry the surface, all under xmb.api.v1:

ServiceWhat it covers
NotificationServiceThe inbox, unread counts, the alert board, the preference grid, and admin broadcasts
NotificationPushServiceRegistering a device for push
NotificationDiscordServiceTying a server to a Discord guild, and linking your own Discord account

Events and alerts

Notifications come in two shapes that never share a list:

  • An event happened at a moment. It lands in an inbox, is read or unread, and ages out after 30 days. "Your request was declined" stays true forever.
  • An alert is a condition that is true right now — a hub offline, storage low, a tracker account failing, playback errors clustering. It carries a severity (info, warn, crit), clears itself when the condition ends, and never becomes history. Current kinds: hub_offline (crit), grabber_offline, storage_low, tracker_auth_failed, playback_failing, task_failing (warn), review_backlog, update_available (info, board-only — never announced).

Players participate in one of these: a player whose pipeline genuinely errors should call StopPlayback with failure_code (short machine code) and failure_detail (the player's own words). A clean stop leaves both empty. Clustered failures on one server raise the playback_failing alert.

Events are read with ListNotifications, which serves two separate feeds per server: the viewer audience (your requests, series you watch, announcements) and the admin audience (requests to approve, sourcing trouble, maintenance). The admin feed requires administering the server. Alerts are read with ListAlerts, admin-only, most severe first.

GetUnreadCounts is the cheap poll behind the bell: viewer unread, admin unread, and the count and loudest severity of active alerts, in one call. Clients typically poll it every 30 seconds. MarkNotificationsRead marks specific notifications (by uuid) or everything in one audience.

Unknown NotificationKind values must render gracefully: every notification carries its own title, body and target_path, so a client never needs per-kind logic — kinds added after your client shipped still display.

Preferences

GetNotificationPreferences returns the caller's grid: rows are categories (my-requests, series, new-in-library, membership, announcements, and the admin-only to-approve, acquisition, server-health, maintenance), columns are channels (in_app, push, email, discord). Each cell is ENABLED, DISABLED, or UNAVAILABLE — unavailable is a fact about the category (the weekly digest never pushes), renders dimmed, and updates against it are rejected.

Preferences are account-scoped: no server slug, one grid that follows you across every server you belong to. UpdateNotificationPreferences takes only the cells being changed and returns the whole resolved grid.

One exception is written into the product: a critical server-health alert (a hub going offline) always reaches admins by push and email, whatever the grid says.

Broadcasts

SendBroadcast (admin) sends a free-form announcement — title, plain-text body, optional in-app link — to every member of a server through their own channel preferences. Setting urgent forces push delivery regardless of preferences; use it sparingly. The response reports how many members it fanned out to.

Push devices

Push registration is account-scoped: a token is a fact about your device and fires for every server you are on.

  1. Call GetPushConfig to learn what the deployment supports — APNs (iOS), FCM (Android) and Web Push availability, plus the VAPID public key a browser needs to subscribe.
  2. Obtain a token: the raw device token from the OS push machinery in the native apps (getDevicePushTokenAsync() — an APNs device token on iOS, an FCM registration token on Android), or PushManager.subscribe() in a browser (serialise the subscription JSON).
  3. Call RegisterPushDevice with a stable per-install install_id, the token kind (APNS, FCM or WEBPUSH), and the token. Registration is an upsert — call it again freely; on every app start with permission granted is correct, and how token rotation is survived.

UnregisterPushDevice withdraws an install's token on sign-out. Tokens the push services report dead are retired automatically.

Don't prompt for notification permission on app load — ask when the person first enables a push preference.

Discord

One platform Discord application serves every server, in two independent ways. When a deployment has no Discord application configured, both surfaces report NOT_CONFIGURED and clients should render nothing.

Tying a server to a guild (admin)

  1. BeginDiscordGuildConnect returns Discord's add-to-server consent URL; the admin picks the guild in Discord's own UI (Discord enforces that they manage it) and is redirected back.
  2. ListDiscordChannels lists the guild's text channels the bot can actually post to, for the channel pickers.
  3. UpdateDiscordChannels routes the two audiences: the admin channel carries the admin feed, the members channel carries server-wide member news (new in library, series drops, announcements). Personal notifications never post to a shared channel.
  4. SendDiscordTest posts a labelled test message to a routed channel and surfaces Discord's exact error when it fails — this is the permission validator to point admins at.

GetDiscordIntegration reports status (including BOT_REMOVED when the bot was kicked and CHANNEL_MISSING when routing points at a deleted channel), and DisconnectDiscordGuild severs the tie.

Linking your own account (anyone)

BeginDiscordAccountLink returns a consent URL for the identify scope; completing it links your Discord identity, after which the discord column of your preference grid delivers by direct message from the bot. The OAuth grant is used once to read your identity and discarded.

GetDiscordLink reports the link, including DM_BLOCKED — the bot cannot message you, usually because your privacy settings block DMs from server members or you share no guild with the bot. DisconnectDiscordAccount forgets the identity.

Messages posted to Discord neutralise mentions and markdown in quoted content, and never include emails, addresses or file paths.

On this page