feat: improve friend circle media and detail rendering

This commit is contained in:
liuwei
2026-04-07 13:04:19 +08:00
parent 2938a6b056
commit dd8c5b1829
2 changed files with 122 additions and 13 deletions

View File

@@ -19,6 +19,8 @@ def _safe_text(value, default=""):
if value is None:
return default
if isinstance(value, dict):
if "buffer" in value:
return _safe_text(value.get("buffer"), default)
if "string" in value:
return _safe_text(value.get("string"), default)
if "text" in value:
@@ -50,13 +52,43 @@ def _extract_list(values):
return []
def _find_timeline_xml(value):
if isinstance(value, str):
return value if "<TimelineObject" in value else ""
if isinstance(value, dict):
for key in ("buffer", "string", "text", "Content", "Xml", "ObjectDesc", "TimelineObject"):
if key in value:
result = _find_timeline_xml(value.get(key))
if result:
return result
for item in value.values():
result = _find_timeline_xml(item)
if result:
return result
elif isinstance(value, list):
for item in value:
result = _find_timeline_xml(item)
if result:
return result
return ""
def _parse_media_from_xml(root):
media_items = []
for media in root.findall(".//mediaList/media"):
media_type = _safe_text(media.findtext("type"))
size_node = media.find("size")
media_items.append({
"url": _safe_text(media.findtext("url")),
"thumb": _safe_text(media.findtext("thumb")),
"id": _safe_text(media.findtext("id"))
"hd": _safe_text(media.findtext("hd")),
"id": _safe_text(media.findtext("id")),
"type": media_type,
"is_video": media_type == "6",
"video_duration": _safe_text(media.findtext("videoDuration")),
"width": _safe_text(size_node.get("width")) if size_node is not None else "",
"height": _safe_text(size_node.get("height")) if size_node is not None else "",
"description": _safe_text(media.findtext("description"))
})
return [item for item in media_items if item.get("url") or item.get("thumb")]
@@ -72,12 +104,24 @@ def _parse_timeline_xml(xml_text):
if create_time.isdigit():
timestamp = datetime.fromtimestamp(int(create_time)).strftime("%Y-%m-%d %H:%M:%S")
location_node = root.find("location")
location = {}
if location_node is not None:
location = {
"poi_name": _safe_text(location_node.get("poiName")),
"poi_address": _safe_text(location_node.get("poiAddress")),
"city": _safe_text(location_node.get("city")),
"latitude": _safe_text(location_node.get("latitude")),
"longitude": _safe_text(location_node.get("longitude"))
}
return {
"id": _safe_text(root.findtext("id")),
"author_wxid": _safe_text(root.findtext("username")),
"content": _safe_text(root.findtext("contentDesc")),
"timestamp": timestamp,
"media": _parse_media_from_xml(root)
"media": _parse_media_from_xml(root),
"location": location
}
@@ -91,11 +135,13 @@ def _normalize_comment(comment):
"comment_id": comment_id,
"author_wxid": (
_safe_text(comment.get("UserName"))
or _safe_text(comment.get("Username"))
or _safe_text(comment.get("username"))
or _safe_text(comment.get("Wxid"))
),
"author_name": (
_safe_text(comment.get("NickName"))
or _safe_text(comment.get("Nickname"))
or _safe_text(comment.get("nickname"))
or _safe_text(comment.get("DisplayName"))
),
@@ -103,7 +149,8 @@ def _normalize_comment(comment):
_safe_text(comment.get("Content"))
or _safe_text(comment.get("content"))
or _safe_text(comment.get("Text"))
)
),
"create_time": _safe_text(comment.get("CreateTime")) or _safe_text(comment.get("createTime"))
}
@@ -125,9 +172,8 @@ def _normalize_like(like):
def _normalize_post(node, server):
xml_payload = ""
for key in ("Content", "Xml", "ObjectDesc", "TimelineObject"):
value = node.get(key)
if isinstance(value, str) and "<TimelineObject" in value:
xml_payload = value
xml_payload = _find_timeline_xml(node.get(key))
if xml_payload:
break
xml_data = _parse_timeline_xml(xml_payload) if xml_payload else {}
@@ -144,6 +190,7 @@ def _normalize_post(node, server):
)
author_name = (
_safe_text(node.get("NickName"))
or _safe_text(node.get("Nickname"))
or _safe_text(node.get("nickname"))
or server.contact_manager.get_nickname(author_wxid)
)
@@ -170,8 +217,15 @@ def _normalize_post(node, server):
for item in media_list:
media.append({
"url": _safe_text(item.get("Url")) or _safe_text(item.get("url")),
"hd": _safe_text(item.get("Hd")) or _safe_text(item.get("hd")),
"thumb": _safe_text(item.get("ThumbUrl")) or _safe_text(item.get("thumb")),
"id": _safe_text(item.get("Id")) or _safe_text(item.get("id"))
"id": _safe_text(item.get("Id")) or _safe_text(item.get("id")),
"type": _safe_text(item.get("Type")) or _safe_text(item.get("type")),
"is_video": (_safe_text(item.get("Type")) or _safe_text(item.get("type"))) == "6",
"video_duration": _safe_text(item.get("VideoDuration")) or _safe_text(item.get("videoDuration")),
"width": _safe_text(item.get("Width")) or _safe_text(item.get("width")),
"height": _safe_text(item.get("Height")) or _safe_text(item.get("height")),
"description": _safe_text(item.get("Description")) or _safe_text(item.get("description"))
})
if not media:
media = xml_data.get("media", [])
@@ -180,6 +234,8 @@ def _normalize_post(node, server):
like_nodes = []
for key in ("CommentList", "Comments", "commentList", "comments"):
comment_nodes.extend(_extract_list(node.get(key)))
for key in ("CommentUserList", "commentUserList"):
comment_nodes.extend(_extract_list(node.get(key)))
for key in ("LikeList", "Likes", "likeList", "likes"):
like_nodes.extend(_extract_list(node.get(key)))
@@ -190,6 +246,7 @@ def _normalize_post(node, server):
"author_avatar": server.contact_manager.get_head_image(author_wxid),
"content": content,
"timestamp": timestamp,
"location": xml_data.get("location", {}),
"media": [item for item in media if item.get("url") or item.get("thumb")],
"comments": [item for item in (_normalize_comment(x) for x in comment_nodes) if item.get("content") or item.get("author_wxid")],
"likes": [item for item in (_normalize_like(x) for x in like_nodes) if item.get("author_wxid")],
@@ -204,7 +261,7 @@ def _extract_posts(payload, server):
if not isinstance(node, dict):
continue
looks_like_post = any(key in node for key in ("Id", "id", "ContentDesc", "CommentList", "LikeList", "ObjectDesc"))
has_timeline_xml = any(isinstance(value, str) and "<TimelineObject" in value for value in node.values())
has_timeline_xml = bool(_find_timeline_xml(node))
if not looks_like_post and not has_timeline_xml:
continue
post = _normalize_post(node, server)