2a74d3888d
Add scrape_wechat_groups (POST /wechatweb/chatroom/getChatroomList) and scrape_wechat_talkers (POST /wechatweb/wxFriends/talkerList), both reusing the page-number paginator. Groups parse the semicolon-joined memberIds into a member_ids_list so upsert_chat_group can mirror GroupMember rows (composite PK channel/room_id/member_id) idempotently alongside the ChatGroup row. Talkers are a sync-planning input (member_count>0 marks a group conversation); no dedicated table. Co-Authored-By: Claude <noreply@anthropic.com>
113 lines
4.0 KiB
Python
113 lines
4.0 KiB
Python
"""Fixture payloads for ``POST /wechatweb/wxFriends/talkerList`` (M2).
|
|
|
|
These records mirror the real field names captured from the live CRM (see the
|
|
data dictionary §2 会话列表 talker: ``friendWechatId, nickName, remarkName,
|
|
messageCount, lastChatTime, headUrl, memberCount, analyseResult``). The
|
|
endpoint is **per-staff-wechat**: the request body carries the ``wechatId`` of
|
|
the owning staff account, and the response lists that account's recent
|
|
conversations (direct chats AND group chats — ``memberCount > 0`` ⇒ group).
|
|
|
|
The envelope carries the row list under ``data.page`` with a boolean
|
|
``data.hasMore`` flag (no ``total`` field is documented for this endpoint)::
|
|
|
|
{"message":"调用成功","code":"10000","success":true,"traceId":"...",
|
|
"data":{"page":[ {<talker record>}, ... ], "hasMore":..}}
|
|
|
|
Because the page-number paginator reuses the empty-page stop signal, the
|
|
cassette ships the records across two pages plus a terminating empty page:
|
|
|
|
* page 1 -> 2 of the 3 fixture talks, ``hasMore=true``
|
|
* page 2 -> the remaining 1 talk, ``hasMore=false``
|
|
* (page 3 -> empty ``page`` array, served inline by the test)
|
|
|
|
Records exercise both the direct-message path (``memberCount == 0``) and the
|
|
group-chat path (``memberCount > 0``) so the orchestrator can distinguish them.
|
|
"""
|
|
|
|
# The module name uses camelCase deliberately to mirror the upstream endpoint
|
|
# name (``talkerList``); the M2 spec mandates this exact filename.
|
|
# ruff: noqa: N999
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
# The owning staff wechat for this talker stream (the ``wechatId`` request
|
|
# field). All records below belong to this account's conversation list.
|
|
OWNER_WECHAT_ID = "wxid_staff_alice01"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Capture-shaped talker (conversation) records (3 total across 2 pages).
|
|
# ---------------------------------------------------------------------------
|
|
|
|
TALKER_RECORDS: list[dict[str, Any]] = [
|
|
{
|
|
# Direct message (1:1 friend). memberCount == 0.
|
|
"friendWechatId": "wxid_friend_0001",
|
|
"nickName": "Alice Wang",
|
|
"remarkName": "Alice-VIP",
|
|
"messageCount": 46,
|
|
"lastChatTime": 1700000000123,
|
|
"headUrl": "https://wx.qlogo.cn/avatar_alice.png",
|
|
"memberCount": 0,
|
|
"displayName": "",
|
|
"analyseResult": "",
|
|
},
|
|
{
|
|
# Group chat. memberCount > 0 ⇒ the friendWechatId is a @chatroom id.
|
|
"friendWechatId": "12345678901@chatroom",
|
|
"nickName": "VIP客户交流群",
|
|
"remarkName": "",
|
|
"messageCount": 4521,
|
|
"lastChatTime": 1700000000456,
|
|
"headUrl": "https://wx.qlogo.cn/room_12345678901.png",
|
|
"memberCount": 3,
|
|
"displayName": "Alice (group)",
|
|
"analyseResult": "",
|
|
},
|
|
{
|
|
# Another direct message — a less active contact.
|
|
"friendWechatId": "wxid_friend_0009",
|
|
"nickName": "Quiet Lead",
|
|
"remarkName": "",
|
|
"messageCount": 2,
|
|
"lastChatTime": 1700000000789,
|
|
"headUrl": "https://wx.qlogo.cn/avatar_quiet.png",
|
|
"memberCount": 0,
|
|
"displayName": "",
|
|
"analyseResult": "",
|
|
},
|
|
]
|
|
|
|
# Default page size for the fixture (and the scraper). 3 records fill exactly
|
|
# 2 pages of size 2 plus a terminating empty page.
|
|
TOTAL = len(TALKER_RECORDS)
|
|
DEFAULT_PAGE_SIZE = 2
|
|
|
|
|
|
def make_talker_list_response(
|
|
records: list[dict[str, Any]],
|
|
*,
|
|
page_num: int = 1,
|
|
page_size: int = DEFAULT_PAGE_SIZE,
|
|
has_more: bool = False,
|
|
trace_id: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Build a capture-shaped ``talkerList`` response envelope.
|
|
|
|
Shape (row list under ``data.page``, boolean ``data.hasMore``)::
|
|
|
|
{"message":"调用成功","code":"10000","success":true,"traceId":"...",
|
|
"data":{"page":[...], "hasMore":..}}
|
|
"""
|
|
return {
|
|
"message": "调用成功",
|
|
"code": "10000",
|
|
"success": True,
|
|
"traceId": trace_id or f"trace-{page_num}",
|
|
"data": {
|
|
"page": list(records),
|
|
"hasMore": has_more,
|
|
},
|
|
}
|