mirror of https://github.com/jackwener/wx-cli.git
fix: 引用消息 XML 转义解析 + 搜索容错跳过 corrupt DB
- 引用消息(type=57)的 ref_content 可能是 HTML 转义的 XML,新增 unescape_html() 先反转义,再递归调用 parse_appmsg 解析嵌套结构 - 全局搜索遍历 msg_db_keys 时,单个 DB open/query 失败改为 eprintln+continue 而非传播错误,避免一个 corrupt cache 导致整个搜索失败 - search_in_table 失败也改为 skip 而非 abortpull/2/head
parent
a6fa82adb3
commit
dfd020a2b9
|
|
@ -209,7 +209,7 @@ pub async fn q_search(
|
|||
let md5_lookup = names.md5_to_uname.clone();
|
||||
let names_map = names.map.clone();
|
||||
|
||||
let table_targets: Vec<(String, String, String, String)> = tokio::task::spawn_blocking(move || {
|
||||
let table_targets: Vec<(String, String, String, String)> = match tokio::task::spawn_blocking(move || {
|
||||
let conn = Connection::open(&path2)?;
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'Msg_%'"
|
||||
|
|
@ -239,7 +239,11 @@ pub async fn q_search(
|
|||
));
|
||||
}
|
||||
Ok::<_, anyhow::Error>(result)
|
||||
}).await??;
|
||||
}).await {
|
||||
Ok(Ok(v)) => v,
|
||||
Ok(Err(e)) => { eprintln!("[search] skip DB {}: {}", rel_key, e); continue; }
|
||||
Err(e) => { eprintln!("[search] task error {}: {}", rel_key, e); continue; }
|
||||
};
|
||||
|
||||
targets.extend(table_targets);
|
||||
}
|
||||
|
|
@ -260,26 +264,35 @@ pub async fn q_search(
|
|||
let limit2 = limit * 3;
|
||||
|
||||
let names_map2 = names.map.clone();
|
||||
let found: Vec<Value> = tokio::task::spawn_blocking(move || {
|
||||
let found: Vec<Value> = match tokio::task::spawn_blocking(move || {
|
||||
let conn = Connection::open(&db_path)?;
|
||||
let mut all = Vec::new();
|
||||
for (tname, display, uname) in &table_list {
|
||||
let is_group = uname.contains("@chatroom");
|
||||
let rows = search_in_table(&conn, tname, &uname, is_group,
|
||||
&names_map2, &kw2, since2, until2, limit2)?;
|
||||
for mut row in rows {
|
||||
if row.get("chat").map(|v| v.as_str().unwrap_or("")).unwrap_or("").is_empty() {
|
||||
if let Some(obj) = row.as_object_mut() {
|
||||
obj.insert("chat".into(), serde_json::Value::String(
|
||||
if display.is_empty() { tname.clone() } else { display.clone() }
|
||||
));
|
||||
match search_in_table(&conn, tname, &uname, is_group,
|
||||
&names_map2, &kw2, since2, until2, limit2)
|
||||
{
|
||||
Ok(rows) => {
|
||||
for mut row in rows {
|
||||
if row.get("chat").map(|v| v.as_str().unwrap_or("")).unwrap_or("").is_empty() {
|
||||
if let Some(obj) = row.as_object_mut() {
|
||||
obj.insert("chat".into(), serde_json::Value::String(
|
||||
if display.is_empty() { tname.clone() } else { display.clone() }
|
||||
));
|
||||
}
|
||||
}
|
||||
all.push(row);
|
||||
}
|
||||
}
|
||||
all.push(row);
|
||||
Err(e) => eprintln!("[search] skip table {}: {}", tname, e),
|
||||
}
|
||||
}
|
||||
Ok::<_, anyhow::Error>(all)
|
||||
}).await??;
|
||||
}).await {
|
||||
Ok(Ok(v)) => v,
|
||||
Ok(Err(e)) => { eprintln!("[search] skip DB: {}", e); continue; }
|
||||
Err(e) => { eprintln!("[search] task error: {}", e); continue; }
|
||||
};
|
||||
|
||||
results.extend(found);
|
||||
}
|
||||
|
|
@ -654,8 +667,18 @@ fn parse_appmsg(text: &str) -> Option<String> {
|
|||
"57" => {
|
||||
let ref_content = extract_xml_text(text, "content")
|
||||
.map(|s| {
|
||||
let s: String = s.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
if s.len() > 80 { format!("{}...", &s[..80]) } else { s }
|
||||
// content 可能是 HTML 转义的 XML(被引用的消息是 appmsg 时)
|
||||
let unescaped = unescape_html(&s);
|
||||
// 如果解转义后是 XML,尝试递归解析
|
||||
if unescaped.contains("<appmsg") {
|
||||
if let Some(parsed) = parse_appmsg(&unescaped) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
let s: String = unescaped.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
if s.chars().count() > 40 {
|
||||
format!("{}...", s.chars().take(40).collect::<String>())
|
||||
} else { s }
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let quote = if !title.is_empty() { format!("[引用] {}", title) } else { "[引用]".into() };
|
||||
|
|
@ -679,6 +702,14 @@ fn extract_xml_text(xml: &str, tag: &str) -> Option<String> {
|
|||
Some(xml[content_start..content_start + end].trim().to_string())
|
||||
}
|
||||
|
||||
fn unescape_html(s: &str) -> String {
|
||||
s.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("&", "&")
|
||||
.replace(""", "\"")
|
||||
.replace("'", "'")
|
||||
}
|
||||
|
||||
fn fmt_time(ts: i64, fmt: &str) -> String {
|
||||
Local.timestamp_opt(ts, 0)
|
||||
.single()
|
||||
|
|
|
|||
Loading…
Reference in New Issue