///|
/// A message-create dispatch with gateway-only message fields.
pub(all) struct MessageCreateEvent {
message : Message
guild_id : GuildId?
guild_member : GuildMember?
mention_members : Map[UserId, GuildMember]
channel_type : ChannelType?
} derive(Debug)
///|
/// A message-update dispatch with the same gateway-only fields as message
/// creation.
pub(all) struct MessageUpdateEvent {
message : Message
/// `None` when decoded via `FromJson`. The bot gateway runtime supplies the
/// cached previous revision only when its messages cache is enabled and that
/// revision is still retained.
before : Message?
guild_id : GuildId?
guild_member : GuildMember?
mention_members : Map[UserId, GuildMember]
channel_type : ChannelType?
} derive(Debug)
///|
/// Whether this update changed the message's edit timestamp.
///
/// Without `before`, this uses the presence of an edited timestamp as a
/// heuristic and may misclassify pin or unfurl updates to an already-edited
/// message as edits.
pub fn MessageUpdateEvent::is_edit(self : MessageUpdateEvent) -> Bool {
match self.before {
Some(before) =>
!edited_timestamps_equal(
before.edited_timestamp,
self.message.edited_timestamp,
)
None => self.message.edited_timestamp is Value(_)
}
}
///|
/// Discord echoes `edited_timestamp` with varying sub-second precision
/// (microseconds on the edit dispatch, milliseconds on later pin or unfurl
/// updates for the same message), so compare instants at millisecond
/// precision rather than strings.
fn edited_timestamps_equal(
a : Nullable[Timestamp],
b : Nullable[Timestamp],
) -> Bool {
match (a, b) {
(Null, Null) => true
(Value(left), Value(right)) => {
let left_ms = left.unix_ms() catch { _ => return left == right }
let right_ms = right.unix_ms() catch { _ => return left == right }
left_ms == right_ms
}
_ => false
}
}
///|
priv struct GatewayMessageMention {
id : UserId
guild_member : GuildMember?
} derive(FromJson(fields(guild_member(rename="member"))))
///|
priv struct GatewayMessageEventFields {
message : Message
guild_id : GuildId?
guild_member : GuildMember?
mention_members : Map[UserId, GuildMember]
channel_type : ChannelType?
}
///|
fn[T : @json.FromJson] optional_message_event_field(
fields : Map[String, Json],
key : String,
path : @json.JsonPath,
) -> T? raise @json.JsonDecodeError {
match fields.get(key) {
None => None
Some(value) => Some(@json.from_json(value, path=path.add_key(key)))
}
}
///|
fn decode_gateway_message_event(
json : Json,
path : @json.JsonPath,
) -> GatewayMessageEventFields raise @json.JsonDecodeError {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected gateway message (object)"))
}
let mentions : Array[GatewayMessageMention] = event_field(
fields, "mentions", path,
)
let mention_members : Map[UserId, GuildMember] = Map([])
for mention in mentions {
if mention.guild_member is Some(guild_member) {
mention_members[mention.id] = guild_member
}
}
{
message: @json.from_json(json, path~),
guild_id: optional_message_event_field(fields, "guild_id", path),
guild_member: optional_message_event_field(fields, "member", path),
mention_members,
channel_type: optional_message_event_field(fields, "channel_type", path),
}
}
///|
pub impl @json.FromJson for MessageCreateEvent with fn from_json(json, path) {
let fields = decode_gateway_message_event(json, path)
{
message: fields.message,
guild_id: fields.guild_id,
guild_member: fields.guild_member,
mention_members: fields.mention_members,
channel_type: fields.channel_type,
}
}
///|
pub impl @json.FromJson for MessageUpdateEvent with fn from_json(json, path) {
let fields = decode_gateway_message_event(json, path)
{
message: fields.message,
before: None,
guild_id: fields.guild_id,
guild_member: fields.guild_member,
mention_members: fields.mention_members,
channel_type: fields.channel_type,
}
}