use std::collections::BTreeMap;
use serde_json::Value;
use crate::discord::{
ChannelInfo, ChannelRecipientInfo, PresenceStatus, ReadStateInfo, RelationshipInfo, RoleInfo,
events::{AppEvent, PresenceEventFields},
ids::{
Id,
marker::{ChannelMarker, GuildMarker, MessageMarker, UserMarker},
},
};
use super::{
channels::{parse_channel_info, parse_channel_recipient_info},
guilds::{
parse_guild_create, parse_guild_onboarding_from_guild, parse_role_info,
parse_user_guild_settings_entries, parse_user_premium_tier,
},
members::{parse_current_user_verification, parse_member_info},
presence::parse_presence_entry,
relationships::parse_relationship_entry,
shared::{display_name_from_parts_or_unknown, parse_id, parse_status},
user_settings::parse_user_settings_info,
voice::parse_guild_voice_states,
};
pub(super) fn parse_ready(data: &Value) -> Vec<AppEvent> {
let mut events = Vec::new();
let mut current_user = None;
let mut current_user_id = None;
let mut current_user_status = None;
if let Some(user) = data.get("user") {
let user_id = user.get("id").and_then(parse_id::<UserMarker>);
let name = display_name_from_parts_or_unknown(
None,
user.get("global_name").and_then(Value::as_str),
user.get("username").and_then(Value::as_str),
);
events.push(AppEvent::Ready {
user: name,
user_id,
});
if let Some(premium_tier) = parse_user_premium_tier(user) {
events.push(AppEvent::CurrentUserCapabilities { premium_tier });
}
if let Some(event) = parse_current_user_verification(user) {
events.push(event);
}
current_user_id = user_id;
current_user = parse_channel_recipient_info(user);
current_user_status = parse_current_user_session_status(data);
if let (Some(user), Some(status)) = (current_user.as_mut(), current_user_status) {
user.status = Some(status);
}
}
if let Some(guilds) = data.get("guilds").and_then(Value::as_array) {
for guild in guilds {
if let Some(event) = parse_guild_create(guild) {
events.push(event);
}
events.extend(parse_guild_voice_states(guild));
}
}
events.extend(parse_merged_member_events(data));
let mut merged_presences = parse_merged_presences(data);
if let Some(presences) = data.get("presences").and_then(Value::as_array) {
merged_presences.extend(
presences
.iter()
.filter_map(parse_presence_entry)
.map(|presence| (presence.user_id, presence)),
);
}
let users_by_id: BTreeMap<Id<UserMarker>, &Value> = data
.get("users")
.and_then(Value::as_array)
.map(|users| {
users
.iter()
.filter_map(|user| {
let id = parse_id::<UserMarker>(user.get("id")?)?;
Some((id, user))
})
.collect()
})
.unwrap_or_default();
let mut ready_users = users_by_id
.values()
.filter_map(|user| parse_channel_recipient_info(user))
.collect::<Vec<_>>();
if let Some(current_user) = current_user.clone()
&& !ready_users
.iter()
.any(|user| user.user_id == current_user.user_id)
{
ready_users.push(current_user);
}
if !ready_users.is_empty() {
events.push(AppEvent::ReadyUserDirectory { users: ready_users });
}
if let Some(privates) = data.get("private_channels").and_then(Value::as_array) {
for channel in privates {
if let Some(mut info) = parse_channel_info(channel, None) {
hydrate_dm_recipients_from_ids(&mut info, channel, &users_by_id);
apply_recipient_presences(&mut info, &merged_presences);
add_current_user_to_group_dm(&mut info, current_user.as_ref());
events.push(AppEvent::ChannelUpsert(info));
}
}
}
if let (Some(user_id), Some(status)) = (current_user_id, current_user_status) {
events.push(AppEvent::PresenceUpdate {
guild_id: None,
presence: PresenceEventFields {
user_id,
status,
activities: Vec::new(),
},
});
}
if let Some(relationships) = data.get("relationships").and_then(Value::as_array) {
let parsed: Vec<RelationshipInfo> = relationships
.iter()
.filter_map(parse_relationship_entry)
.collect();
if !parsed.is_empty() {
events.push(AppEvent::RelationshipsLoaded {
relationships: parsed,
});
}
}
if let Some(entries) = data
.get("read_state")
.and_then(|node| node.get("entries").or(Some(node)))
.and_then(Value::as_array)
{
let parsed: Vec<ReadStateInfo> =
entries.iter().filter_map(parse_read_state_entry).collect();
if !parsed.is_empty() {
events.push(AppEvent::ReadStateInit { entries: parsed });
}
}
if let Some(settings) = parse_user_guild_settings_entries(data.get("user_guild_settings")) {
events.push(AppEvent::UserGuildSettingsInit { settings });
}
if let Some(flags) = data
.get("notification_settings")
.and_then(|settings| settings.get("flags"))
.and_then(Value::as_u64)
{
events.push(AppEvent::UserNotificationSettingsUpdate { flags });
}
if let Some(settings) = data.get("user_settings").and_then(parse_user_settings_info) {
events.push(AppEvent::UserSettingsUpdate { settings });
}
events
}
pub(super) fn parse_ready_supplemental(data: &Value) -> Vec<AppEvent> {
let mut events = parse_supplemental_guild_events(data);
events.extend(parse_merged_member_events(data));
events.extend(parse_merged_presences(data).into_values().map(|presence| {
AppEvent::PresenceUpdate {
guild_id: None,
presence,
}
}));
if let Some(channels) = data.get("lazy_private_channels").and_then(Value::as_array) {
for raw_channel in channels {
let Some(channel) = parse_channel_info(raw_channel, None) else {
continue;
};
let recipient_ids = raw_channel
.get("recipient_ids")
.and_then(Value::as_array)
.map(|ids| {
ids.iter()
.filter_map(parse_id::<UserMarker>)
.collect::<Vec<_>>()
})
.unwrap_or_default();
if recipient_ids.is_empty() {
events.push(AppEvent::ChannelUpsert(channel));
} else {
events.push(AppEvent::LazyPrivateChannelUpsert {
channel,
recipient_ids,
});
}
}
}
events
}
fn parse_merged_member_events(data: &Value) -> Vec<AppEvent> {
let Some(guilds) = data.get("guilds").and_then(Value::as_array) else {
return Vec::new();
};
let Some(merged_members) = data.get("merged_members").and_then(Value::as_array) else {
return Vec::new();
};
guilds
.iter()
.zip(merged_members)
.flat_map(|(guild, members)| {
let Some(guild_id) = guild.get("id").and_then(parse_id::<GuildMarker>) else {
return Vec::new();
};
members
.as_array()
.map(|members| guild_member_upsert_events(guild_id, members))
.unwrap_or_default()
})
.collect()
}
fn parse_supplemental_guild_events(data: &Value) -> Vec<AppEvent> {
let Some(guilds) = data.get("guilds").and_then(Value::as_array) else {
return Vec::new();
};
let mut events = Vec::new();
for guild in guilds {
let Some(guild_id) = guild.get("id").and_then(parse_id::<GuildMarker>) else {
continue;
};
if let Some(onboarding) = parse_guild_onboarding_from_guild(guild, guild_id) {
events.push(AppEvent::GuildOnboardingUpdate {
guild_id,
onboarding,
});
}
if let Some(roles) = guild.get("roles").and_then(Value::as_array) {
let roles: Vec<RoleInfo> = roles.iter().filter_map(parse_role_info).collect();
if !roles.is_empty() {
events.push(AppEvent::GuildRolesUpdate { guild_id, roles });
}
}
if let Some(channels) = guild.get("channels").and_then(Value::as_array) {
events.extend(
channels
.iter()
.filter_map(|channel| parse_channel_info(channel, Some(guild_id)))
.map(AppEvent::ChannelUpsert),
);
}
if let Some(threads) = guild.get("threads").and_then(Value::as_array) {
events.extend(
threads
.iter()
.filter_map(|channel| parse_channel_info(channel, Some(guild_id)))
.map(AppEvent::ChannelUpsert),
);
}
if let Some(members) = guild.get("members").and_then(Value::as_array) {
events.extend(guild_member_upsert_events(guild_id, members));
}
if let Some(member) = guild
.get("member")
.and_then(|member| parse_member_info(member, Some(guild_id)))
{
events.push(AppEvent::GuildMemberUpsert { guild_id, member });
}
if let Some(presences) = guild.get("presences").and_then(Value::as_array) {
events.extend(
presences
.iter()
.filter_map(parse_presence_entry)
.map(|presence| AppEvent::PresenceUpdate {
guild_id: Some(guild_id),
presence,
}),
);
}
events.extend(parse_guild_voice_states(guild));
}
events
}
fn guild_member_upsert_events(guild_id: Id<GuildMarker>, members: &[Value]) -> Vec<AppEvent> {
members
.iter()
.filter_map(|member| parse_member_info(member, Some(guild_id)))
.map(|member| AppEvent::GuildMemberUpsert { guild_id, member })
.collect()
}
type MergedPresences = BTreeMap<Id<UserMarker>, PresenceEventFields>;
fn parse_merged_presences(data: &Value) -> MergedPresences {
let mut presences = MergedPresences::new();
if let Some(merged) = data.get("merged_presences") {
collect_presence_entries(merged, &mut presences);
}
presences
}
fn parse_current_user_session_status(data: &Value) -> Option<PresenceStatus> {
data.get("sessions")
.and_then(Value::as_array)
.and_then(|sessions| {
sessions.iter().find_map(|session| {
let status = session
.get("status")
.and_then(Value::as_str)
.map(parse_status)?;
(status != PresenceStatus::Unknown).then_some(status)
})
})
}
fn collect_presence_entries(value: &Value, presences: &mut MergedPresences) {
if let Some(presence) = parse_presence_entry(value) {
presences.insert(presence.user_id, presence);
return;
}
if let Some(items) = value.as_array() {
for item in items {
collect_presence_entries(item, presences);
}
} else if let Some(object) = value.as_object() {
for item in object.values() {
collect_presence_entries(item, presences);
}
}
}
fn apply_recipient_presences(channel: &mut ChannelInfo, presences: &MergedPresences) {
let Some(recipients) = channel.recipients.as_mut() else {
return;
};
for recipient in recipients {
if let Some(presence) = presences.get(&recipient.user_id) {
recipient.status = Some(presence.status);
}
}
}
fn hydrate_dm_recipients_from_ids(
channel: &mut ChannelInfo,
raw: &Value,
users_by_id: &BTreeMap<Id<UserMarker>, &Value>,
) {
if !matches!(channel.kind.as_str(), "dm" | "group-dm") {
return;
}
if channel
.recipients
.as_ref()
.is_some_and(|recipients| !recipients.is_empty())
{
return;
}
let Some(ids) = raw.get("recipient_ids").and_then(Value::as_array) else {
return;
};
let resolved: Vec<ChannelRecipientInfo> = ids
.iter()
.filter_map(parse_id::<UserMarker>)
.filter_map(|user_id| {
let user = users_by_id.get(&user_id)?;
parse_channel_recipient_info(user)
})
.collect();
if resolved.is_empty() {
return;
}
let synthetic_label = format!("dm-{}", channel.channel_id.get());
if channel.name == synthetic_label {
channel.name = resolved
.iter()
.map(|recipient| recipient.display_name.as_str())
.collect::<Vec<_>>()
.join(", ");
}
channel.recipients = Some(resolved);
}
fn add_current_user_to_group_dm(
channel: &mut ChannelInfo,
current_user: Option<&ChannelRecipientInfo>,
) {
if channel.kind != "group-dm" {
return;
}
let Some(current_user) = current_user else {
return;
};
let Some(recipients) = channel.recipients.as_mut() else {
return;
};
if recipients
.iter()
.any(|recipient| recipient.user_id == current_user.user_id)
{
return;
}
recipients.push(current_user.clone());
}
fn parse_read_state_entry(value: &Value) -> Option<ReadStateInfo> {
let channel_id = parse_id::<ChannelMarker>(value.get("id")?)?;
Some(ReadStateInfo {
read_state_type: value
.get("read_state_type")
.and_then(Value::as_u64)
.and_then(|value| u8::try_from(value).ok())
.unwrap_or(0),
channel_id,
last_acked_message_id: value
.get("last_message_id")
.or_else(|| value.get("last_acked_id"))
.and_then(parse_id::<MessageMarker>),
mention_count: value
.get("mention_count")
.and_then(Value::as_u64)
.unwrap_or(0) as u32,
badge_count: value
.get("badge_count")
.and_then(Value::as_u64)
.unwrap_or(0) as u32,
last_pin_timestamp: value
.get("last_pin_timestamp")
.and_then(Value::as_str)
.map(str::to_owned),
flags: value.get("flags").and_then(Value::as_u64).unwrap_or(0),
last_viewed: value.get("last_viewed").and_then(Value::as_u64),
})
}