use crate::state::ChannelId;
const NOTICE_TYPES: &[&str] = &[
"TIME",
"NOTICE",
"MEETING_NOTICE",
"SYSTEM_KR_EDIT",
"SYSTEM_KR_STATE_EDIT",
"SYSTEM_KR_VOTE",
"SYSTEM_KR_PUBLISH",
"SYSTEM_MONTH_ORIENT_SHARE",
"SYSTEM",
"SYSTEN",
];
fn is_system_notice(msg_type: &str) -> bool {
let upper = msg_type.to_ascii_uppercase();
NOTICE_TYPES.contains(&upper.as_str())
}
fn resolve_quick_reply(raw: &str, props: &serde_json::Value) -> Option<serde_json::Value> {
if !raw.is_empty() {
return serde_json::from_str::<serde_json::Value>(raw)
.ok()
.filter(serde_json::Value::is_array);
}
props
.get("quickReply")
.filter(|value| value.is_array())
.cloned()
}
fn resolve_reactions(quick_reply: Option<&serde_json::Value>) -> Option<String> {
let arr = quick_reply?.as_array()?;
let emojis: Vec<&str> = arr
.iter()
.filter_map(|e| e.get("emoji").and_then(|v| v.as_str()))
.filter(|s| !s.is_empty())
.collect();
if emojis.is_empty() {
None
} else {
Some(emojis.join(","))
}
}
fn resolve_reaction_count(quick_reply: Option<&serde_json::Value>) -> usize {
quick_reply
.and_then(serde_json::Value::as_array)
.map(|items| {
items
.iter()
.map(|item| {
item.get("userIds")
.or_else(|| item.get("user_ids"))
.and_then(|value| value.as_array())
.map(Vec::len)
.unwrap_or(0)
})
.sum()
})
.unwrap_or(0)
}
fn resolve_template_received(props: &serde_json::Value) -> bool {
props
.get("template")
.and_then(|t| t.get("userIds"))
.and_then(|u| u.as_array())
.map(|a| !a.is_empty())
.unwrap_or(false)
}
fn resolve_template_confirmed_count(props: &serde_json::Value) -> usize {
props
.get("template")
.and_then(|value| value.get("userIds").or_else(|| value.get("user_ids")))
.and_then(|value| value.as_array())
.map(Vec::len)
.unwrap_or(0)
}
fn resolve_template_reader_ids(props: &serde_json::Value) -> Vec<&str> {
props
.get("template")
.and_then(|value| value.get("userIds").or_else(|| value.get("user_ids")))
.and_then(serde_json::Value::as_array)
.into_iter()
.flatten()
.filter_map(serde_json::Value::as_str)
.filter(|id| !id.is_empty())
.collect()
}
fn resolve_pinned(props: &serde_json::Value) -> Option<bool> {
props.get("pinned").and_then(|v| v.as_bool())
}
fn parse_json_field(raw: &str, fallback: serde_json::Value) -> serde_json::Value {
if raw.is_empty() {
fallback
} else {
serde_json::from_str::<serde_json::Value>(raw)
.ok()
.filter(|value| !value.is_null())
.unwrap_or(fallback)
}
}
pub(super) fn message_item_data(
channel_id: ChannelId,
event_seq: u64,
msg_id: &str,
fields: &crate::sync_session::PostFields,
viewer_user_id: &str,
_revoked: bool,
) -> serde_json::Value {
use serde_json::json;
let props: serde_json::Value = if fields.props.is_empty() {
json!({})
} else {
serde_json::from_str(&fields.props).unwrap_or_else(|_| json!({}))
};
let topic: serde_json::Value = if fields.topic.is_empty() {
json!({})
} else {
serde_json::from_str(&fields.topic).unwrap_or_else(|_| json!({}))
};
let expedite_map: serde_json::Value = if fields.expedite_map.is_empty() {
json!({})
} else {
serde_json::from_str(&fields.expedite_map).unwrap_or_else(|_| json!({}))
};
let quick_reply = resolve_quick_reply(fields.quick_reply.as_str(), &props);
let is_self = !viewer_user_id.is_empty() && fields.user_id == viewer_user_id;
let has_server_id = !msg_id.is_empty();
let urgent =
crate::query::render_ready::urgent::project(&expedite_map, viewer_user_id, has_server_id);
let msg_type = if fields.msg_type.is_empty() {
"TEXT"
} else {
fields.msg_type.as_str()
};
let forward_detail = crate::query::render_ready::forward::detail(msg_type, &props);
let user_snapshot = crate::query::render_ready::core::normalize_user_snapshot(
&parse_json_field(&fields.user_snapshot, json!({})),
);
let replied_message = crate::query::render_ready::core::normalize_replied_message(
&parse_json_field(&fields.replied_message, serde_json::Value::Null),
);
let urgent_fields = json!({
"urgent": urgent.required_count > 0,
"urgentTargetCount": urgent.required_count,
"urgentConfirmedCount": urgent.confirmed_ids.len(),
"urgentRequesterId": urgent.requester_id,
"urgentTargetIds": urgent.target_ids,
"urgentConfirmedIds": urgent.confirmed_ids,
"urgentRequiredCount": urgent.required_count,
"urgentState": urgent.state,
"canConfirmUrgent": urgent.can_confirm,
});
let mut data = json!({
"channel_id": channel_id.as_str(),
"event_seq": event_seq,
"msg_id": msg_id,
"id": msg_id,
"msgId": msg_id,
"temporaryId": fields.temporary_id.as_str(),
"channelId": channel_id.as_str(),
"eventSeq": event_seq,
"userId": fields.user_id.as_str(),
"userSnapshot": user_snapshot,
"type": msg_type,
"message": fields.message.as_str(),
"simpleMessage": crate::message_summary::resolve(msg_type, &fields.message, &props, &fields.simple_message, false),
"text": fields.message.as_str(),
"props": props,
"forwardDetail": forward_detail,
"topic": topic,
"createAt": fields.create_at,
"createdAt": fields.create_at,
"updateAt": if fields.update_at > 0 { fields.update_at } else { fields.create_at },
"readBits": fields.read_bits.as_str(),
"viewers": fields.viewers,
"mentions": fields.mentions,
"sendStatus": "sent",
"serverId": if has_server_id { serde_json::Value::String(msg_id.to_string()) } else { serde_json::Value::Null },
"isSelf": is_self,
"reactions": resolve_reactions(quick_reply.as_ref()),
"reactionCount": resolve_reaction_count(quick_reply.as_ref()),
"expediteMap": expedite_map,
"templateReceived": resolve_template_received(&props),
"templateConfirmedCount": resolve_template_confirmed_count(&props),
"readerIds": resolve_template_reader_ids(&props),
"systemNotice": is_system_notice(msg_type),
"pinned": resolve_pinned(&props),
"replyId": fields.reply_id.as_str(),
"replyRootId": fields.reply_root_id.as_str(),
"replyFirstLevelId": fields.reply_first_level_id.as_str(),
"repliedMessage": replied_message,
"replyMessages": parse_json_field(&fields.reply_messages, json!([])),
"replyCount": fields.reply_count,
});
if let (serde_json::Value::Object(data), serde_json::Value::Object(urgent_fields)) =
(&mut data, urgent_fields)
{
data.extend(urgent_fields);
}
if let (serde_json::Value::Object(data), Some(quick_reply)) = (&mut data, quick_reply) {
data.insert("quickReply".to_string(), quick_reply);
}
data
}