108 lines
3.6 KiB
Python
108 lines
3.6 KiB
Python
import random
|
|
import time
|
|
from typing import Iterable
|
|
|
|
|
|
def _safe_float(value) -> float:
|
|
try:
|
|
return float(value or 0)
|
|
except (TypeError, ValueError):
|
|
return 0.0
|
|
|
|
|
|
def generate_timeline_id() -> str:
|
|
return f"{int(time.time() * 1000)}{random.randint(1000, 9999)}"
|
|
|
|
|
|
def build_media_xml(media_items: Iterable[dict]) -> str:
|
|
parts = []
|
|
for index, item in enumerate(media_items):
|
|
url = item.get("url", "")
|
|
thumb = item.get("thumb", url)
|
|
media_id = item.get("id") or f"{int(time.time() * 1000)}{index}"
|
|
md5 = item.get("md5", "")
|
|
total_size = item.get("total_size", 0)
|
|
width = item.get("width", 0)
|
|
height = item.get("height", 0)
|
|
parts.append(
|
|
f"""
|
|
<media>
|
|
<id><![CDATA[{media_id}]]></id>
|
|
<type><![CDATA[2]]></type>
|
|
<title></title>
|
|
<description></description>
|
|
<private><![CDATA[0]]></private>
|
|
<url type="1" md5="{md5}"><![CDATA[{url}]]></url>
|
|
<thumb type="1"><![CDATA[{thumb}]]></thumb>
|
|
<videoDuration><![CDATA[0.0]]></videoDuration>
|
|
<size totalSize="{_safe_float(total_size):.1f}" width="{_safe_float(width):.1f}" height="{_safe_float(height):.1f}"></size>
|
|
</media>
|
|
""".strip()
|
|
)
|
|
return "\n".join(parts)
|
|
|
|
|
|
def build_friend_circle_xml(wxid: str, content: str, media_items: list[dict] | None = None,
|
|
timeline_id: str | None = None, create_time: int | None = None) -> str:
|
|
media_items = media_items or []
|
|
timeline_id = timeline_id or generate_timeline_id()
|
|
create_time = create_time or int(time.time())
|
|
content_style = 1 if media_items else 2
|
|
media_xml = build_media_xml(media_items)
|
|
|
|
return f"""
|
|
<TimelineObject>
|
|
<id><![CDATA[{timeline_id}]]></id>
|
|
<username><![CDATA[{wxid}]]></username>
|
|
<createTime><![CDATA[{create_time}]]></createTime>
|
|
<contentDescShowType>0</contentDescShowType>
|
|
<contentDescScene>0</contentDescScene>
|
|
<private><![CDATA[0]]></private>
|
|
<contentDesc><![CDATA[{content}]]></contentDesc>
|
|
<contentattr><![CDATA[0]]></contentattr>
|
|
<sourceUserName></sourceUserName>
|
|
<publicUserName></publicUserName>
|
|
<sourceNickName></sourceNickName>
|
|
<statisticsData></statisticsData>
|
|
<weappInfo>
|
|
<appUserName></appUserName>
|
|
<pagePath></pagePath>
|
|
<version><![CDATA[0]]></version>
|
|
<isHidden>0</isHidden>
|
|
<debugMode><![CDATA[0]]></debugMode>
|
|
<shareActionId></shareActionId>
|
|
<isGame><![CDATA[0]]></isGame>
|
|
<messageExtraData></messageExtraData>
|
|
<subType><![CDATA[0]]></subType>
|
|
<preloadResources></preloadResources>
|
|
</weappInfo>
|
|
<canvasInfoXml></canvasInfoXml>
|
|
<ContentObject>
|
|
<contentStyle><![CDATA[{content_style}]]></contentStyle>
|
|
<contentSubStyle><![CDATA[0]]></contentSubStyle>
|
|
<title></title>
|
|
<description></description>
|
|
<contentUrl></contentUrl>
|
|
<mediaList>
|
|
{media_xml}
|
|
</mediaList>
|
|
</ContentObject>
|
|
<actionInfo>
|
|
<appMsg>
|
|
<mediaTagName></mediaTagName>
|
|
<messageExt></messageExt>
|
|
<messageAction></messageAction>
|
|
</appMsg>
|
|
</actionInfo>
|
|
<appInfo>
|
|
<id></id>
|
|
</appInfo>
|
|
<location poiClassifyId="" poiName="" poiAddress="" poiClassifyType="0" city=""></location>
|
|
<streamvideo>
|
|
<streamvideourl></streamvideourl>
|
|
<streamvideothumburl></streamvideothumburl>
|
|
<streamvideoweburl></streamvideoweburl>
|
|
</streamvideo>
|
|
</TimelineObject>
|
|
""".strip()
|