feat: expose url field for link/appmsg messages

Extract <url> from appmsg XML in type-49 messages and append it as
a 'url' field in history/search output. The field is omitted when
the message has no valid URL (non-link types, empty, non-http).
pull/18/head
tsinghu 2026-04-21 15:03:26 +08:00 committed by jackwener
parent b0431352ce
commit 58d06eda23
1 changed files with 33 additions and 4 deletions

View File

@ -549,15 +549,20 @@ fn query_messages(
let content = decompress_message(&content_bytes, ct); let content = decompress_message(&content_bytes, ct);
let sender = sender_label(real_sender_id, &content, is_group, chat_username, &id2u, names_map, group_nicknames); let sender = sender_label(real_sender_id, &content, is_group, chat_username, &id2u, names_map, group_nicknames);
let text = fmt_content(local_id, local_type, &content, is_group); let text = fmt_content(local_id, local_type, &content, is_group);
let url = appmsg_url_for_message(local_type, &content);
result.push(json!({ let mut msg = json!({
"timestamp": ts, "timestamp": ts,
"time": fmt_time(ts, "%Y-%m-%d %H:%M"), "time": fmt_time(ts, "%Y-%m-%d %H:%M"),
"sender": sender, "sender": sender,
"content": text, "content": text,
"type": fmt_type(local_type), "type": fmt_type(local_type),
"local_id": local_id, "local_id": local_id,
})); });
if let Some(u) = url {
msg["url"] = serde_json::Value::String(u);
}
result.push(msg);
} }
Ok(result) Ok(result)
} }
@ -636,15 +641,20 @@ fn search_in_table(
if search_decoded_content && !matches_search_text(&content, &text, keyword, &keyword_lower) { if search_decoded_content && !matches_search_text(&content, &text, keyword, &keyword_lower) {
continue; continue;
} }
let url = appmsg_url_for_message(local_type, &content);
result.push(json!({ let mut msg = json!({
"timestamp": ts, "timestamp": ts,
"time": fmt_time(ts, "%Y-%m-%d %H:%M"), "time": fmt_time(ts, "%Y-%m-%d %H:%M"),
"chat": "", "chat": "",
"sender": sender, "sender": sender,
"content": text, "content": text,
"type": fmt_type(local_type), "type": fmt_type(local_type),
})); });
if let Some(u) = url {
msg["url"] = serde_json::Value::String(u);
}
result.push(msg);
if search_decoded_content && result.len() >= limit { if search_decoded_content && result.len() >= limit {
break; break;
} }
@ -1273,6 +1283,25 @@ fn extract_xml_text(xml: &str, tag: &str) -> Option<String> {
Some(xml[content_start..content_start + end].trim().to_string()) Some(xml[content_start..content_start + end].trim().to_string())
} }
/// 从 appmsg XML 中提取链接 URL优先取 <url>fallback 到 <url1>
fn extract_appmsg_url(text: &str) -> Option<String> {
// 群消息前缀 "wxid_xxx:\n" 需先剥离
let xml = if text.contains(":\n") {
text.splitn(2, ":\n").nth(1).unwrap_or(text)
} else {
text
};
if !xml.contains("<appmsg") {
return None;
}
let url = extract_xml_text(xml, "url")
.or_else(|| extract_xml_text(xml, "url1"))?;
if url.is_empty() || !url.starts_with("http") {
return None;
}
Some(url)
}
fn extract_xml_attr(xml: &str, tag: &str, attr: &str) -> Option<String> { fn extract_xml_attr(xml: &str, tag: &str, attr: &str) -> Option<String> {
let open = format!("<{}", tag); let open = format!("<{}", tag);
let start = xml.find(&open)?; let start = xml.find(&open)?;