///|
/// The user fragment carried by a presence update.
///
/// Discord only normally requires `id`, but explicitly warns that presence
/// fields and their types are not validated. Keeping the id optional lets a
/// malformed user fragment remain observable without rejecting the event.
pub(all) struct PresenceUser {
id : UserId?
} derive(ToJson, Debug)
///|
/// A user's overall presence state.
pub(all) enum PresenceStatus {
Idle
Dnd
Online
Offline
Unknown(String)
} derive(Eq, Debug)
///|
pub fn PresenceStatus::to_string(self : PresenceStatus) -> String {
match self {
Idle => "idle"
Dnd => "dnd"
Online => "online"
Offline => "offline"
Unknown(value) => value
}
}
///|
pub fn PresenceStatus::from_string(value : String) -> PresenceStatus {
match value {
"idle" => Idle
"dnd" => Dnd
"online" => Online
"offline" => Offline
value => Unknown(value)
}
}
///|
pub impl ToJson for PresenceStatus with fn to_json(self) {
Json::string(self.to_string())
}
///|
pub impl @json.FromJson for PresenceStatus with fn from_json(json, path) {
match json {
String(value) => PresenceStatus::from_string(value.to_string())
_ => raise JsonDecodeError((path, "expected presence status (string)"))
}
}
///|
/// The Discord client families on which a user is active.
pub(all) struct ClientStatus {
desktop : PresenceStatus?
mobile : PresenceStatus?
web : PresenceStatus?
} derive(ToJson, Debug)
///|
/// An activity category reported in a presence update.
pub(all) enum ActivityType {
Playing
Streaming
Listening
Watching
Custom
Competing
Unknown(Int)
} derive(Eq, Debug)
///|
pub fn ActivityType::to_int(self : ActivityType) -> Int {
match self {
Playing => 0
Streaming => 1
Listening => 2
Watching => 3
Custom => 4
Competing => 5
Unknown(value) => value
}
}
///|
pub fn ActivityType::from_int(value : Int) -> ActivityType {
match value {
0 => Playing
1 => Streaming
2 => Listening
3 => Watching
4 => Custom
5 => Competing
value => Unknown(value)
}
}
///|
pub impl ToJson for ActivityType with fn to_json(self) {
Json::number(self.to_int().to_double())
}
///|
pub impl @json.FromJson for ActivityType with fn from_json(json, path) {
match json {
Number(value, ..) => ActivityType::from_int(value.to_int())
_ => raise JsonDecodeError((path, "expected activity type (number)"))
}
}
///|
/// A Discord presence activity, including the documented Rich Presence fields.
pub(all) struct Activity {
name : String
typ : ActivityType
url : Nullable[String]?
created_at : Int64
timestamps : ActivityTimestamps?
application_id : ApplicationId?
status_display_type : Nullable[Int]?
details : Nullable[String]?
details_url : Nullable[String]?
state : Nullable[String]?
state_url : Nullable[String]?
emoji : Nullable[ActivityEmoji]?
party : ActivityParty?
assets : ActivityAssets?
secrets : ActivitySecrets?
instance : Bool?
flags : ActivityFlags?
buttons : Array[ActivityButton]?
} derive(ToJson(fields(typ(rename="type"))), Debug)
///|
struct ActivityWire {
name : String
typ : ActivityType
url : Nullable[String]?
created_at : Double
timestamps : ActivityTimestamps?
application_id : ApplicationId?
status_display_type : Nullable[Int]?
details : Nullable[String]?
details_url : Nullable[String]?
state : Nullable[String]?
state_url : Nullable[String]?
emoji : Nullable[ActivityEmoji]?
party : ActivityParty?
assets : ActivityAssets?
secrets : ActivitySecrets?
instance : Bool?
flags : ActivityFlags?
buttons : Array[ActivityButton]?
} derive(FromJson(fields(typ(rename="type"))))
///|
pub impl @json.FromJson for Activity with fn from_json(json, path) {
let wire : ActivityWire = @json.from_json(json, path~)
{
name: wire.name,
typ: wire.typ,
url: wire.url,
created_at: wire.created_at.to_int64(),
timestamps: wire.timestamps,
application_id: wire.application_id,
status_display_type: wire.status_display_type,
details: wire.details,
details_url: wire.details_url,
state: wire.state,
state_url: wire.state_url,
emoji: wire.emoji,
party: wire.party,
assets: wire.assets,
secrets: wire.secrets,
instance: wire.instance,
flags: wire.flags,
buttons: wire.buttons,
}
}
///|
/// Unix millisecond start and end times of an activity.
pub(all) struct ActivityTimestamps {
start : Int64?
end : Int64?
} derive(Debug)
///|
struct ActivityTimestampsWire {
start : Double?
end : Double?
} derive(FromJson)
///|
pub impl ToJson for ActivityTimestamps with fn to_json(self) {
let fields : Map[String, Json] = Map([])
if self.start is Some(value) {
fields["start"] = Json::number(value.to_double(), repr=value.to_string())
}
if self.end is Some(value) {
fields["end"] = Json::number(value.to_double(), repr=value.to_string())
}
Json::object(fields)
}
///|
pub impl @json.FromJson for ActivityTimestamps with fn from_json(json, path) {
let wire : ActivityTimestampsWire = @json.from_json(json, path~)
{
start: wire.start.map(value => value.to_int64()),
end: wire.end.map(value => value.to_int64()),
}
}
///|
/// The custom or unicode emoji attached to a custom status.
pub(all) struct ActivityEmoji {
name : String
id : EmojiId?
animated : Bool?
} derive(ToJson, FromJson, Debug)
///|
/// The rich-presence party, with `size` as `[current, max]` when present.
pub(all) struct ActivityParty {
id : String?
size : Array[Int]?
} derive(ToJson, FromJson, Debug)
///|
/// Rich-presence artwork references and their hover texts.
pub(all) struct ActivityAssets {
large_image : String?
large_text : String?
large_url : String?
small_image : String?
small_text : String?
small_url : String?
invite_cover_image : String?
} derive(ToJson, FromJson, Debug)
///|
/// Secrets for joining or spectating a rich-presence session.
pub(all) struct ActivitySecrets {
join : String?
spectate : String?
match_ : String?
} derive (
ToJson(fields(match_(rename="match"))),
FromJson(fields(match_(rename="match"))),
Debug,
)
///|
/// A custom label/url button shown on a rich presence.
pub(all) struct ActivityButton {
label : String
url : String
} derive(ToJson, FromJson, Debug)
///|
/// Bit set describing which rich-presence interactions an activity supports.
pub(all) struct ActivityFlags(UInt64) derive(Eq)
///|
pub fn ActivityFlags::from_bits(bits : UInt64) -> ActivityFlags {
ActivityFlags(bits)
}
///|
pub fn ActivityFlags::bits(self : ActivityFlags) -> UInt64 {
self.0
}
///|
pub impl Debug for ActivityFlags with fn to_repr(self) {
Repr(self.0)
}
///|
pub impl ToJson for ActivityFlags with fn to_json(self) {
Json::number(self.0.to_double(), repr=self.0.to_string())
}
///|
pub impl @json.FromJson for ActivityFlags with fn from_json(json, path) {
match json {
Number(_, repr=Some(repr)) =>
ActivityFlags(
@string.parse_uint64(repr.to_string()) catch {
_ => raise JsonDecodeError((path, "expected activity flags (number)"))
},
)
Number(value, repr=None) => ActivityFlags(value.to_uint64())
_ => raise JsonDecodeError((path, "expected activity flags (number)"))
}
}
///|
/// A gateway presence update decoded field-by-field.
///
/// Discord does not validate the presence payload. A missing field or a field
/// whose value has the wrong type is represented as `None`; one bad field never
/// prevents the rest of the event from being delivered.
pub(all) struct PresenceUpdateEvent {
user : PresenceUser?
guild_id : GuildId?
status : PresenceStatus?
activities : Array[Activity]?
client_status : ClientStatus?
} derive(ToJson, Debug)
///|
fn[T : @json.FromJson] lenient_presence_field(
fields : Map[String, Json],
key : String,
path : @json.JsonPath,
) -> T? {
match fields.get(key) {
None => None
Some(value) =>
Some(@json.from_json(value, path=path.add_key(key))) catch {
_ => None
}
}
}
///|
pub impl @json.FromJson for PresenceUser with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected presence user (object)"))
}
{ id: lenient_presence_field(fields, "id", path), }
}
///|
pub impl @json.FromJson for ClientStatus with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected client status (object)"))
}
{
desktop: lenient_presence_field(fields, "desktop", path),
mobile: lenient_presence_field(fields, "mobile", path),
web: lenient_presence_field(fields, "web", path),
}
}
///|
pub impl @json.FromJson for PresenceUpdateEvent with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected presence update (object)"))
}
{
user: lenient_presence_field(fields, "user", path),
guild_id: lenient_presence_field(fields, "guild_id", path),
status: lenient_presence_field(fields, "status", path),
activities: lenient_presence_field(fields, "activities", path),
client_status: lenient_presence_field(fields, "client_status", path),
}
}