use serde_json::Value;
use std::sync::Arc;
use crate::discord::{
ChannelInfo, ChannelNotificationOverrideInfo, CustomEmojiInfo, GuildBoostTier,
GuildNotificationSettingsInfo, GuildOnboardingInfo, GuildOnboardingMode,
GuildVerificationLevel, NotificationLevel, PremiumTier, RoleInfo, UserGuildSettingsInfo,
events::AppEvent,
ids::{
Id,
marker::{ChannelMarker, EmojiMarker, GuildMarker, RoleMarker, UserMarker},
},
};
use super::{
channels::parse_channel_info, members::parse_member_info, presence::parse_presence_entry,
shared::parse_id,
};
pub(super) fn parse_guild_create(data: &Value) -> Option<AppEvent> {
let guild_id = parse_id::<GuildMarker>(data.get("id")?)?;
let name = guild_field(data, "name")
.and_then(Value::as_str)
.unwrap_or("unknown")
.to_owned();
let mut channels: Vec<ChannelInfo> = data
.get("channels")
.and_then(Value::as_array)
.map(|items| {
items
.iter()
.filter_map(|channel| parse_channel_info(channel, Some(guild_id)))
.collect()
})
.unwrap_or_default();
if let Some(threads) = data.get("threads").and_then(Value::as_array) {
channels.extend(
threads
.iter()
.filter_map(|channel| parse_channel_info(channel, Some(guild_id))),
);
}
let members = data
.get("members")
.and_then(Value::as_array)
.map(|items| {
items
.iter()
.filter_map(|member| parse_member_info(member, Some(guild_id)))
.collect()
})
.unwrap_or_default();
let member_count = data.get("member_count").and_then(Value::as_u64);
let presences = data
.get("presences")
.and_then(Value::as_array)
.map(|items| {
items
.iter()
.filter_map(parse_presence_entry)
.map(|presence| (presence.user_id, presence.status))
.collect()
})
.unwrap_or_default();
let roles = data
.get("roles")
.and_then(Value::as_array)
.map(|items| items.iter().filter_map(parse_role_info).collect());
let emojis = data
.get("emojis")
.and_then(Value::as_array)
.map(|items| items.iter().filter_map(parse_custom_emoji).collect())
.unwrap_or_default();
let owner_id = guild_field(data, "owner_id").and_then(parse_id::<UserMarker>);
let boost_tier = parse_guild_boost_tier(data);
let boost_count = parse_guild_boost_count(data).unwrap_or(0);
let verification_level = parse_guild_verification_level(data);
let mfa_level = parse_guild_mfa_level(data);
let features = parse_guild_features(data);
let onboarding = parse_guild_onboarding_from_guild(data, guild_id);
Some(AppEvent::GuildCreate {
guild_id,
name,
member_count,
owner_id,
boost_tier,
boost_count,
verification_level,
mfa_level,
features,
onboarding,
channels,
members,
presences,
roles,
emojis,
})
}
fn parse_guild_boost_tier(data: &Value) -> GuildBoostTier {
guild_field(data, "premium_tier")
.and_then(Value::as_u64)
.map_or(GuildBoostTier::None, GuildBoostTier::from_premium_tier)
}
fn parse_guild_boost_count(data: &Value) -> Option<u32> {
guild_field(data, "premium_subscription_count")
.and_then(Value::as_u64)
.and_then(|count| u32::try_from(count).ok())
}
fn parse_guild_verification_level(data: &Value) -> Option<GuildVerificationLevel> {
guild_field(data, "verification_level")
.and_then(Value::as_u64)
.map(GuildVerificationLevel::from_value)
}
fn parse_guild_mfa_level(data: &Value) -> Option<u64> {
guild_field(data, "mfa_level").and_then(Value::as_u64)
}
fn parse_guild_features(data: &Value) -> Option<Vec<String>> {
guild_field(data, "features")
.and_then(Value::as_array)
.map(|features| {
features
.iter()
.filter_map(Value::as_str)
.map(str::to_owned)
.collect()
})
}
pub(super) fn parse_guild_onboarding_from_guild(
data: &Value,
guild_id: Id<GuildMarker>,
) -> Option<GuildOnboardingInfo> {
guild_field(data, "guild_onboarding")
.or_else(|| guild_field(data, "onboarding"))
.and_then(|onboarding| parse_guild_onboarding(onboarding, Some(guild_id)))
}
fn parse_guild_onboarding(
value: &Value,
fallback_guild_id: Option<Id<GuildMarker>>,
) -> Option<GuildOnboardingInfo> {
let guild_id = value
.get("guild_id")
.and_then(parse_id::<GuildMarker>)
.or(fallback_guild_id)?;
let enabled = value.get("enabled").and_then(Value::as_bool);
let mode = value
.get("mode")
.and_then(Value::as_u64)
.map(GuildOnboardingMode::from_value);
let default_channel_ids = value
.get("default_channel_ids")
.and_then(Value::as_array)
.map(|channel_ids| {
channel_ids
.iter()
.filter_map(parse_id::<ChannelMarker>)
.collect()
})
.unwrap_or_default();
Some(GuildOnboardingInfo {
guild_id,
enabled,
mode,
default_channel_ids,
raw: Arc::new(value.clone()),
})
}
pub(super) fn parse_guild_onboarding_update(data: &Value) -> Option<AppEvent> {
let guild_id = data.get("guild_id").and_then(parse_id::<GuildMarker>)?;
let value = data
.get("guild_onboarding")
.or_else(|| data.get("onboarding"))
.unwrap_or(data);
let onboarding = parse_guild_onboarding(value, Some(guild_id))?;
Some(AppEvent::GuildOnboardingUpdate {
guild_id,
onboarding,
})
}
pub(super) fn parse_role_info(value: &Value) -> Option<RoleInfo> {
let id = parse_id::<RoleMarker>(value.get("id")?)?;
let name = value
.get("name")
.and_then(Value::as_str)
.filter(|value| !value.is_empty())?
.to_owned();
let color = value
.get("colors")
.and_then(|colors| colors.get("primary_color"))
.and_then(Value::as_u64)
.or_else(|| value.get("color").and_then(Value::as_u64))
.and_then(|value| u32::try_from(value).ok())
.filter(|value| *value != 0);
let position = value.get("position").and_then(Value::as_i64).unwrap_or(0);
let hoist = value.get("hoist").and_then(Value::as_bool).unwrap_or(false);
let permissions = value
.get("permissions")
.and_then(|value| {
value
.as_str()
.and_then(|s| s.parse::<u64>().ok())
.or_else(|| value.as_u64())
})
.unwrap_or(0);
Some(RoleInfo {
id,
name,
color,
position,
hoist,
permissions,
})
}
fn parse_custom_emoji(value: &Value) -> Option<CustomEmojiInfo> {
let id = parse_id::<EmojiMarker>(value.get("id")?)?;
let name = value
.get("name")
.and_then(Value::as_str)
.filter(|value| !value.is_empty())?
.to_owned();
let animated = value
.get("animated")
.and_then(Value::as_bool)
.unwrap_or(false);
let available = value
.get("available")
.and_then(Value::as_bool)
.unwrap_or(true);
Some(CustomEmojiInfo {
id,
name,
animated,
available,
})
}
pub(super) fn parse_user_premium_tier(user: &Value) -> Option<PremiumTier> {
user.get("premium_type")
.and_then(Value::as_u64)
.map(PremiumTier::from_premium_type)
}
pub(super) fn parse_guild_emojis_update(data: &Value) -> Option<AppEvent> {
let guild_id = parse_id::<GuildMarker>(data.get("guild_id")?)?;
let emojis = data
.get("emojis")
.and_then(Value::as_array)
.map(|items| items.iter().filter_map(parse_custom_emoji).collect())
.unwrap_or_default();
Some(AppEvent::GuildEmojisUpdate { guild_id, emojis })
}
pub(super) fn parse_guild_role_upsert(data: &Value) -> Option<AppEvent> {
Some(AppEvent::GuildRoleUpsert {
guild_id: parse_id::<GuildMarker>(data.get("guild_id")?)?,
role: parse_role_info(data.get("role")?)?,
})
}
pub(super) fn parse_guild_role_delete(data: &Value) -> Option<AppEvent> {
Some(AppEvent::GuildRoleDelete {
guild_id: parse_id::<GuildMarker>(data.get("guild_id")?)?,
role_id: parse_id::<RoleMarker>(data.get("role_id")?)?,
})
}
pub(super) fn parse_guild_update(data: &Value) -> Option<AppEvent> {
let guild_id = parse_id::<GuildMarker>(data.get("id")?)?;
let name = guild_field(data, "name")
.and_then(Value::as_str)
.unwrap_or("unknown")
.to_owned();
let emojis = data
.get("emojis")
.and_then(Value::as_array)
.map(|items| items.iter().filter_map(parse_custom_emoji).collect());
let roles = data
.get("roles")
.and_then(Value::as_array)
.map(|items| items.iter().filter_map(parse_role_info).collect());
let owner_id = guild_field(data, "owner_id").and_then(parse_id::<UserMarker>);
let boost_tier = guild_field(data, "premium_tier")
.and_then(Value::as_u64)
.map(GuildBoostTier::from_premium_tier);
let boost_count = parse_guild_boost_count(data);
let verification_level = parse_guild_verification_level(data);
let mfa_level = parse_guild_mfa_level(data);
let features = parse_guild_features(data);
let onboarding = parse_guild_onboarding_from_guild(data, guild_id);
Some(AppEvent::GuildUpdate {
guild_id,
name,
owner_id,
boost_tier,
boost_count,
verification_level,
mfa_level,
features,
onboarding,
roles,
emojis,
})
}
pub(super) fn parse_guild_delete(data: &Value) -> Option<AppEvent> {
let guild_id = parse_id::<GuildMarker>(data.get("id")?)?;
if data
.get("unavailable")
.and_then(Value::as_bool)
.unwrap_or(false)
{
Some(AppEvent::GuildUnavailable { guild_id })
} else {
Some(AppEvent::GuildDelete { guild_id })
}
}
pub(super) fn parse_user_guild_settings_update(data: &Value) -> Option<AppEvent> {
parse_user_guild_settings_info(data)
.map(|settings| AppEvent::UserGuildSettingsUpdate { settings })
}
pub(super) fn parse_user_guild_settings_entries(
value: Option<&Value>,
) -> Option<Vec<UserGuildSettingsInfo>> {
let entries = value
.and_then(|node| node.get("entries").or(Some(node)))
.and_then(Value::as_array)?;
let settings: Vec<UserGuildSettingsInfo> = entries
.iter()
.filter_map(parse_user_guild_settings_info)
.collect();
(!settings.is_empty()).then_some(settings)
}
fn parse_user_guild_settings_info(value: &Value) -> Option<UserGuildSettingsInfo> {
Some(UserGuildSettingsInfo {
notification_settings: parse_user_guild_notification_settings(value)?,
extra_fields: parse_extra_user_guild_settings_fields(value),
})
}
fn parse_user_guild_notification_settings(value: &Value) -> Option<GuildNotificationSettingsInfo> {
let guild_id = parse_user_guild_settings_guild_id(value.get("guild_id"))?;
let channel_overrides = parse_channel_notification_overrides(value.get("channel_overrides"));
Some(GuildNotificationSettingsInfo {
guild_id,
message_notifications: parse_notification_level(value.get("message_notifications")),
muted: value.get("muted").and_then(Value::as_bool).unwrap_or(false),
mute_end_time: parse_mute_end_time(value),
suppress_everyone: value
.get("suppress_everyone")
.and_then(Value::as_bool)
.unwrap_or(false),
suppress_roles: value
.get("suppress_roles")
.and_then(Value::as_bool)
.unwrap_or(false),
flags: value.get("flags").and_then(Value::as_u64).unwrap_or(0),
hide_muted_channels: value
.get("hide_muted_channels")
.and_then(Value::as_bool)
.unwrap_or(false),
mobile_push: value
.get("mobile_push")
.and_then(Value::as_bool)
.unwrap_or(true),
mute_scheduled_events: value
.get("mute_scheduled_events")
.and_then(Value::as_bool)
.unwrap_or(false),
notify_highlights: value
.get("notify_highlights")
.and_then(Value::as_u64)
.unwrap_or(0),
version: value.get("version").and_then(Value::as_u64).unwrap_or(0),
channel_overrides,
})
}
fn parse_user_guild_settings_guild_id(value: Option<&Value>) -> Option<Option<Id<GuildMarker>>> {
match value {
Some(value) if value.is_null() => Some(None),
Some(value) if value.as_str() == Some("@me") => Some(None),
Some(value) => parse_id::<GuildMarker>(value).map(Some),
None => Some(None),
}
}
fn parse_channel_notification_overrides(
value: Option<&Value>,
) -> Vec<ChannelNotificationOverrideInfo> {
match value {
Some(Value::Array(overrides)) => overrides
.iter()
.filter_map(parse_channel_notification_override)
.collect(),
Some(Value::Object(overrides)) => overrides
.iter()
.filter_map(|(channel_id, override_value)| {
parse_channel_notification_override_with_key(channel_id, override_value)
})
.collect(),
_ => Vec::new(),
}
}
fn parse_channel_notification_override(value: &Value) -> Option<ChannelNotificationOverrideInfo> {
Some(ChannelNotificationOverrideInfo {
channel_id: value
.get("channel_id")
.and_then(parse_id::<ChannelMarker>)?,
message_notifications: parse_notification_level(value.get("message_notifications")),
muted: value.get("muted").and_then(Value::as_bool).unwrap_or(false),
mute_end_time: parse_mute_end_time(value),
collapsed: value
.get("collapsed")
.and_then(Value::as_bool)
.unwrap_or(false),
flags: value.get("flags").and_then(Value::as_u64).unwrap_or(0),
})
}
fn parse_channel_notification_override_with_key(
channel_id: &str,
value: &Value,
) -> Option<ChannelNotificationOverrideInfo> {
Some(ChannelNotificationOverrideInfo {
channel_id: channel_id.parse::<u64>().ok().and_then(Id::new_checked)?,
message_notifications: parse_notification_level(value.get("message_notifications")),
muted: value.get("muted").and_then(Value::as_bool).unwrap_or(false),
mute_end_time: parse_mute_end_time(value),
collapsed: value
.get("collapsed")
.and_then(Value::as_bool)
.unwrap_or(false),
flags: value.get("flags").and_then(Value::as_u64).unwrap_or(0),
})
}
fn parse_notification_level(value: Option<&Value>) -> Option<NotificationLevel> {
value
.and_then(Value::as_u64)
.and_then(NotificationLevel::from_code)
}
fn parse_mute_end_time(value: &Value) -> Option<String> {
value
.get("mute_config")
.and_then(|config| config.get("end_time"))
.and_then(Value::as_str)
.filter(|value| !value.is_empty())
.map(str::to_owned)
}
fn guild_field<'a>(data: &'a Value, key: &str) -> Option<&'a Value> {
data.get(key).or_else(|| {
data.get("properties")
.and_then(|properties| properties.get(key))
})
}
fn parse_extra_user_guild_settings_fields(
value: &Value,
) -> std::collections::BTreeMap<String, Value> {
let Some(settings) = value.as_object() else {
return std::collections::BTreeMap::new();
};
settings
.iter()
.filter(|(field, _)| !is_known_user_guild_settings_field(field))
.map(|(field, value)| (field.clone(), value.clone()))
.collect()
}
fn is_known_user_guild_settings_field(field: &str) -> bool {
matches!(
field,
"guild_id"
| "message_notifications"
| "muted"
| "mute_config"
| "suppress_everyone"
| "suppress_roles"
| "channel_overrides"
)
}