Watch
1
0
Fork
You've already forked mautrix-discord
0
mirror of https://github.com/mautrix/discord.git synced 2026-08-22 12:04:54 -04:00
mautrix-discord/pkg/connector/vitals.md
2026-07-16 16:39:01 -07:00

14 KiB

Vitals

Each user login on the bridge (i.e. each DiscordClient) collates a number of account-level health and safety signals into an (effectively) immutable structure named vitals.

The main goals of this abstraction are to (eventually):

  • Aid in tracing the cause of rare failures; vitals are logged every time they are evaluated.
  • Have an easily serializable bag of data that can be used to provide a rough measure of how "at risk" the user account is.
  • Automatically inform bridge operation by helping determine which API/Gateway actions are likely to fail. (This is not yet implemented.)
  • Define simple logic around whether "user intervention" is required (which prompts the bridge to halt all outgoing activity and enter BAD_CREDENTIALS in order to protect the user's account and be a good API citizen).

Signals

The following is an exhaustive enumeration of the fields that are present on the vitals struct, which is reported under the info.vitals field of the mautrix bridge state. For example:

"info": {
  // (... other, arbitrary data ...)
  "vitals": {
    "flagged_as_spammer": false,
    "has_phone": false,
    "has_unread_system_messages": false,
    "quarantined": false,
    "required_action": "AGREEMENTS",
    "safety": {
      "affected_by_spam_classification": false,
      "standing": 100
    },
    "verified_email": true
  }
}

(Because required_action is a "hard" signal, as described below, this vitals state implies that the bridge is in BAD_CREDENTIALS.)

A ? character following the type of a field indicates that the field may be entirely absent (i.e. not merely null) under the described circumstances.

"Inert" Signals

Inert signals are passive attributes that may explain certain failures.

verified_email boolean

  • Presence/nullability: Always present
  • Origin: user object's verified field
  • Live updating: Unknown (however, correctly incorporated if it were to be sent via USER_UPDATE)
  • Effect on bridge: Likely to cause backfill failures (TODO: should eventually pause backfill queues when this happens)

User accounts that lack a verified email are very likely to receive API code error 40002 ("You need to verify your account in order to perform this action").

Note that first-party clients strongly encourage you to verify your email in many UI surfaces immediately after registration.

has_phone boolean

  • Presence/nullability: Always present
  • Origin: presence of non-empty string on the user object's phone field
  • Live updating: Unknown (however, correctly incorporated if it were to be sent via USER_UPDATE)
  • Effect on bridge: None

Denotes whether the user has a phone number associated with their account that has been verified (via e.g. SMS).

"Soft" Signals

Signals explicitly applied by Discord that may or may not impede bridge operation. These are reported because they would be significant in the event of bridge malfunction, and useful for reconstructing the cause of a failure.

quarantined boolean

  • Presence/nullability: Always present
  • Origin: bit 1 << 44 (QUARANTINED) on the user object's flags bit field
  • Live updating: Unknown (however, correctly incorporated if it were to be sent via USER_UPDATE)
  • Effect on bridge: None (TODO: should eventually affect bridge operation)

Discord's automated heuristics may decide to place arbitrary restrictions on users' accounts, "quarantining" them. This places limitations on the following operations (this list is likely incomplete):

  • Sending outgoing friend requests
  • Sending direct messages to friends added while quarantined
  • Joining new guilds ("servers")
  • Creating private channels (starting new direct messages)

Direct messages may be sent as usual to friends who were added before the QUARANTINED flag was applied.

For more information, see:

flagged_as_spammer boolean

  • Presence/nullability: Always present
  • Origin: bit 1 << 20 (SPAMMER) on the user object's flags bit field
  • Live updating: Unknown (however, correctly incorporated if it were to be sent via USER_UPDATE)
  • Effect on bridge: None

Other users see messages from a SPAMMER-flagged account as collapsed by default. However, other SPAMMER-flagged accounts see them normally.

This flag can be applied automatically and heuristically, even to user accounts with verified phone numbers (where has_phone is true).

safety object?

This is a sub-object directly under vitals.

It is completely absent when the bridge configuration's report_scrubbed_account_standing field is false (which is the default value). Otherwise, the object itself is present once a Safety Hub fetch has succeeded.

Note that "scrubbed" merely refers to the omission of any personally identifiable information in the following fields; Safety Hub classification data inherently encapsulates sensitive data as it replicates any offending content for the user to see (in first-party clients; the bridge's data modeling does not make an effort to deserialize the sensitive fields).

safety.standing integer
  • Presence/nullability: Always present with parent
  • Origin: Safety Hub (GET /api/v…/safety-hub/@me)
  • Live updating: Yes (based on best-effort, unverified heuristics)
  • Effect on bridge: None (TODO: should eventually affect bridge operation)

A number that quantifies the user account's standing with Discord as visible in the "Safety Hub". The currently known values are:

Value User facing description in first-party clients Note
100 "Your account is all good" Natural state
200 "Your account is limited"
300 "Your account is very limited"
400 "Your account is at risk"
500 "Your account is suspended"
safety.affected_by_spam_classification boolean
  • Presence/nullability: Always present with parent
  • Origin: Safety Hub (GET /api/v…/safety-hub/@me)
  • Live updating: Yes (based on best-effort, unverified heuristics)
  • Effect on bridge: None

Denotes whether an active spam classification currently applies to the user account. This does not include classifications that were caused by guild membership or ownership.

TODO: The bridge needs to poke its vitals once a classification's expiration time is reached.

"Hard" Signals

All "hard" signals immediately prevent further usage of the bridge. When a "hard" signal is detected, the bridge is immediately put into BAD_CREDENTIALS and most outgoing requests to Discord fail with ErrNotLoggedIn (see Bridge State). This condition is also internally referred to as "requiring user intervention" (RequiresUserIntervention).

The bridge tries its best to continue bridging incoming events, especially since it is important that the urgent Discord system message be made visible to the user as soon as possible. However, all outgoing operations such as message sending, editing, deletion, reactions, etc. will fail.

Whether user intervention is currently required is not directly reported in the bridge state's info, but is logged at runtime. A BAD_CREDENTIALS state can be used to infer if intervention is needed.

required_action string?

  • Presence/nullability: Entirely absent when not applicable
  • Origin: required_action field on the READY payload received from the Gateway
  • Live updating: Yes (via USER_REQUIRED_ACTION_UPDATE)
  • Effect on bridge: Requires user intervention

A required action is applied to a user account when an interactive security or safety flow must be completed before the account may be further used.

The currently known values are:

required_action Bridge state error code Resolution process in a first-party client
AGREEMENTS dc-require-agreements Read and review potential terms of service and/or policy updates
REQUIRE_CAPTCHA (legacy; not expected in practice) (unmapped) N/A
REQUIRE_VERIFIED_EMAIL dc-require-verified-email Add a verified email
REQUIRE_VERIFIED_PHONE dc-require-verified-phone Add a verified phone number
REQUIRE_REVERIFIED_EMAIL dc-require-reverified-email Reaffirm ownership of existing email
REQUIRE_REVERIFIED_PHONE dc-require-reverified-phone Reaffirm ownership of existing phone number
REQUIRE_VERIFIED_EMAIL_OR_VERIFIED_PHONE dc-require-verified-email-or-verified-phone Add a verified phone number or email
REQUIRE_REVERIFIED_EMAIL_OR_VERIFIED_PHONE dc-require-reverified-email-or-verified-phone Reaffirm ownership of existing email, or add a verified phone number
REQUIRE_VERIFIED_EMAIL_OR_REVERIFIED_PHONE dc-require-verified-email-or-reverified-phone Add a verified email, or reaffirm ownership of existing phone number
REQUIRE_REVERIFIED_EMAIL_OR_REVERIFIED_PHONE dc-require-reverified-email-or-reverified-phone Reaffirm ownership of existing email or phone number
REQUIRE_SAFETY_FLOWS dc-require-safety-flows Proceed through server-driven safety flow UI (age verification, etc.)

(Note that the AGREEMENTS value lacks REQUIRE_ despite containing it in the error code.)

has_unread_system_messages boolean

  • Presence/nullability: Always present
  • Origin: bit 1 << 13 (HAS_UNREAD_URGENT_MESSAGES) on the user object's flags bit field
  • Live updating: Yes (via MESSAGE_CREATE)
  • Effect on bridge: Requires user intervention

Denotes that a user has unread "urgent" messages from Discord's official system account.

These are currently known to be sent when the account standing has changed.

Implementation

As of 2026-07-16 it has been determined that the first-party client synchronously mutates the in-memory (Flux) user object to incorporate this flag when an "urgent" message is received:

function Q(e) {
	let {message: t} = e;
	if ((L(t, !0), null != t.flags && r.Lt(t.flags, A.pr7.URGENT))) {
		let e = T[m.default.getId()];
		return (
			null != e &&
			((T[m.default.getId()] = e.set(
				"flags",
				r.lA(e.flags, A.nhx.HAS_UNREAD_URGENT_MESSAGES, !0),
			)),
			!0)
		);
	}
	return !1;
}

This behavior is replicated in our synchronous state handling path. We wait for the user to use a first-party client to read their system messages, which leads to this flag being removed and a resulting USER_UPDATE event on the Gateway.

The bridge could theoretically remove the flag itself, but we have opted not to do this at this time as it is likely to lead to an in-app CAPTCHA challenge that is most easily resolved in Discord's client.

Bridge State

On the usual bridge state update path (i.e. the one that is responsible for reporting CONNECTED), vitals are always consulted. If user intervention is required, the bridge enters the BAD_CREDENTIALS state and most (if not all) outgoing operations that make requests to Discord will fail with ErrNotLoggedIn. The reported UserAction is always UserActionOpenNative.

Barring any bugs, the bridge should automatically respond to any resolution performed by the user in the first-party client, according to the "live updating" field specified on each signal.

Error Codes

A bridge state may only report a single error at a time, so unread system messages currently take priority over any required action. Whether this corresponds to the actual behavior in first-party clients is currently unverified.

The error code for has_unread_system_messages being true is dc-unread-system-messages. Each required action has a corresponding error code that is described in the required_action of this document.

Lifecycle

Vitals are "poked" (reevaluated) on the following events:

  • Gateway READY (successfully connected with a fresh state snapshot).
  • When the user's object is updated via Gateway USER_UPDATE, such as the flags bit field.
    • Critically, this handles the HAS_UNREAD_URGENT_MESSAGES flag being removed from another client.
  • When an urgent system message from Discord is received.
  • When the user's required action changes (USER_REQUIRED_ACTION_UPDATE).

As part of the reevaluation, the last fetched Safety Hub information (if present) is incorporated into the vitals and the heuristics that follow.

After reevaluation:

  • A bridge state is unconditionally sent to mautrix.
    • Instead of trying to be clever about when a new bridge state is truly needed, we implicitly rely on mautrix's deduplication logic to avoid excessive updates.
  • If needed, a "full sync" is kicked off in the background (say, if the bridge started off with bad vitals and never had a chance to perform a full sync).

Safety Hub

Safety Hub information is fetched on the following events when the bridge configuration allows it, before vitals are poked:

  • Asynchronously when an urgent system message is received.
    • When your account standing changes for whatever reason, Discord sends a system message that is flagged as urgent.
  • Gateway READY.
  • Gateway RESUMED.

The Gateway seemingly does not dedicate an event to Safety Hub information changing, so the bridge must fetch it opportunistically.