mirror of
https://github.com/sinanyuntu/trade-message-center.git
synced 2026-09-17 13:22:11 +08:00
feat: normalize OneTalk business card content
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# OneTalk 名片消息:运行态格式观察
|
||||
|
||||
> 观察日期:2026-09-11
|
||||
> 证据边界:现有已登录 OneTalk PWA 的 Chromium CDP,只读调用页面 SDK;不保存或输出原始消息、联系人资料、令牌、加密标识或正文。
|
||||
> 性质:单个真实样本的运行态观察,不是当前跨层数据合同。
|
||||
|
||||
## 1. 怎么判断是名片
|
||||
|
||||
SDK 历史条目的名片判别条件为:
|
||||
|
||||
```ts
|
||||
const isBusinessCardMessage = (message: Record<string, unknown>): boolean =>
|
||||
message.messageType === "rec" &&
|
||||
message.type === 1 &&
|
||||
message.viewType === 0 &&
|
||||
message.msgType === 10010 &&
|
||||
message.subType === 57 &&
|
||||
message.originalData?.cardType === 1;
|
||||
```
|
||||
|
||||
这里的联合条件不可缩减成 `msgType=10010`:附件、询盘和订单也使用该 `msgType`。
|
||||
|
||||
## 2. JSON 中已有的数据
|
||||
|
||||
### 2.1 名片卡片参数
|
||||
|
||||
```text
|
||||
originalData.cardType = 1
|
||||
originalData.params keys =
|
||||
ctime, from, showCertifications, showCompanyName,
|
||||
showEmailAddress, sign, to
|
||||
```
|
||||
|
||||
`showCompanyName`、`showEmailAddress` 和 `showCertifications` 是展示开关;它们不是公司名、邮箱或认证详情本身。
|
||||
|
||||
### 2.2 会话联系人对象
|
||||
|
||||
同一条 SDK 条目的 `contact` 中可观察到以下候选字段:
|
||||
|
||||
```text
|
||||
accountId, accountIdEncrypt, aliId, aliIdEncrypt,
|
||||
loginId, loginIdEncrypt, name, fullPortrait,
|
||||
companyName, complianceCountryCode, currentTimeZone, serviceType
|
||||
```
|
||||
|
||||
可谨慎使用的资料含义如下:
|
||||
|
||||
| 字段 | 可表达的信息 | 限制 |
|
||||
| ------------------------------- | ------------- | ---------------------------------------- |
|
||||
| `contact.name` | 联系人显示名 | 是会话联系人资料,不能证明是名片固定字段 |
|
||||
| `contact.companyName` | 联系人公司名 | 可为空或滞后 |
|
||||
| `contact.complianceCountryCode` | 国家/地区代码 | 国旗由 UI 按代码渲染,不是消息图片 |
|
||||
| `contact.fullPortrait` | 头像候选 URL | 本样本未填;不能假设必有 |
|
||||
|
||||
截图中的邮箱没有观察到独立的 `email` JSON 字段。本样本 `content` 是非 JSON 的普通字符串;邮箱可能出现在其中的展示文本,但没有验证出可复用的字段格式。
|
||||
|
||||
`extInfo.icbuData` 同样只有 `chatEvent` 有实际值,`title`、`iconUrl`、`cardUrls`、`actions`、`defaultContent` 都不可直接使用。
|
||||
|
||||
## 3. 怎么获取
|
||||
|
||||
名片和其它业务卡共享同一只读历史入口;区别只在过滤条件。
|
||||
|
||||
```js
|
||||
const getBusinessCardMessages = async (conversation) => {
|
||||
const service = window.IcbuIM.IMBaaSSDK.default.getMessageService();
|
||||
const response = await service.fetchMessagesWithoutUpdateToRead(
|
||||
{
|
||||
conversationCode: conversation.cid,
|
||||
contactAccountId: conversation.accountId,
|
||||
contactAccountIdEncrypt: conversation.accountIdEncrypt,
|
||||
aliId: conversation.aliId,
|
||||
aliIdEncrypt: conversation.aliIdEncrypt,
|
||||
searchMessageId: "",
|
||||
timeSlide: { forward: false, timeStamp: Date.now(), pageSize: 20 },
|
||||
},
|
||||
conversation,
|
||||
);
|
||||
|
||||
return (response.list ?? []).filter(
|
||||
(message) =>
|
||||
message.messageType === "rec" &&
|
||||
message.type === 1 &&
|
||||
message.viewType === 0 &&
|
||||
message.msgType === 10010 &&
|
||||
message.subType === 57 &&
|
||||
message.originalData?.cardType === 1,
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
如果后续业务需要联系人名称、公司、国家代码或头像,必须明确它们是 `contact` 资料观察,不是名片内容的权威声明。跨层同步只应传递经业务批准的白名单字段;不能透传 `contact` 整体对象、加密标识、`chatToken`、`content` 或 `sign`。
|
||||
|
||||
## 4. 已验证与未覆盖
|
||||
|
||||
- 已验证:`10010/57/cardType=1` 判别组合,`originalData.params` 键集合,联系人候选资料和 `icbuData` 可用性。
|
||||
- 未覆盖:`content` 的名片展示文本格式,独立邮箱字段来源,头像字段在不同名片中的填充率,以及名片详情/跳转链接。
|
||||
@@ -0,0 +1,101 @@
|
||||
# OneTalk 询盘消息:运行态格式观察
|
||||
|
||||
> 观察日期:2026-09-11
|
||||
> 证据边界:现有已登录 OneTalk PWA 的 Chromium CDP,只读调用页面 SDK;不切换会话、不更新已读状态、不保存或输出原始消息、令牌、加密标识、媒体 URL 或正文。
|
||||
> 性质:单个真实样本的运行态观察,不是 OneTalk 全局类型枚举,也不是当前跨层数据合同。
|
||||
|
||||
## 1. 怎么判断是询盘
|
||||
|
||||
SDK 历史返回的单条扁平消息必须同时满足以下条件:
|
||||
|
||||
```ts
|
||||
const isInquiryMessage = (message: Record<string, unknown>): boolean =>
|
||||
message.messageType === "rec" &&
|
||||
message.type === 1 &&
|
||||
message.viewType === 0 &&
|
||||
message.msgType === 10010 &&
|
||||
message.subType === 50 &&
|
||||
message.originalData?.cardType === 6;
|
||||
```
|
||||
|
||||
其中 `msgType=10010` 不是充分条件:当前已观察到的附件、名片和订单同样使用它。
|
||||
|
||||
| 类型 | `msgType` | `subType` | `originalData.cardType` |
|
||||
| ---- | --------: | --------: | ----------------------: |
|
||||
| 询盘 | 10010 | 50 | 6 |
|
||||
| 名片 | 10010 | 57 | 1 |
|
||||
| 订单 | 10010 | 59 | 9 |
|
||||
| 附件 | 10010 | 61 | 12 |
|
||||
|
||||
## 2. JSON 中已有的数据
|
||||
|
||||
SDK 返回是 `{ hasMore, list }`,本样本的询盘条目具有以下顶层字段:
|
||||
|
||||
```text
|
||||
autoReply, contact, contactRead, content, conversationCode, extInfo,
|
||||
localExt, messageId, messageType, msgType, opId, originExt,
|
||||
originalData, owner, receiver, sendTime, sender, spamStatus, status,
|
||||
subType, type, unread, uuid, viewType
|
||||
```
|
||||
|
||||
询盘专有的稳定结构位于 `originalData`:
|
||||
|
||||
```text
|
||||
originalData.cardType = 6
|
||||
originalData.params keys =
|
||||
ctime, encryFeedbackId, encryTradeId, fbType, from, marketType,
|
||||
sign, source, to, version
|
||||
```
|
||||
|
||||
这些字段可用于将消息关联到询盘/贸易实体,但 `encryFeedbackId`、`encryTradeId` 和 `sign` 属于不应跨 MAIN-world 边界的敏感原始值。
|
||||
|
||||
`extInfo.icbuData` 具有 `actions`、`cardUrls`、`title`、`iconUrl`、`defaultContent`、`chatEvent` 等候选键名;本样本实际只有 `chatEvent` 有值。不能把键存在误认为标题、商品图片或详情链接可直接取得。
|
||||
|
||||
本样本的 `content` 是普通字符串,不是 JSON、URI-JSON 或 Base64-JSON。它不是稳定的业务字段合同。
|
||||
|
||||
## 3. 展示卡与 SDK 字段的差异
|
||||
|
||||
页面渲染的询盘卡可显示商品标题、采购量、需求、商品图片和按钮,但这些并非本次 SDK 对象中直接可用的结构化字段:
|
||||
|
||||
- 商品标题、采购量、详细需求和图片可以在**已渲染且当前可见**的卡片 DOM 中读取;这只是 UI 观察,不可代替消息事实源。
|
||||
- 本样本的 SDK `cardUrls` 为空,卡片内也没有静态 `<a href>`;“查看详情 / 立即报价”由前端点击逻辑生成,不能从原始消息直接宣称存在详情链接。
|
||||
- 不应通过 DOM 抓取的展示文本反推稳定的后端协议字段。
|
||||
|
||||
## 4. 怎么获取
|
||||
|
||||
在 OneTalk PWA 的 MAIN world 中,从精确会话对象调用只读历史方法。不要从昵称、列表顺序或页面文本推断 `conversationId`。
|
||||
|
||||
```js
|
||||
const getInquiryMessages = async (conversation) => {
|
||||
const service = window.IcbuIM.IMBaaSSDK.default.getMessageService();
|
||||
const response = await service.fetchMessagesWithoutUpdateToRead(
|
||||
{
|
||||
conversationCode: conversation.cid,
|
||||
contactAccountId: conversation.accountId,
|
||||
contactAccountIdEncrypt: conversation.accountIdEncrypt,
|
||||
aliId: conversation.aliId,
|
||||
aliIdEncrypt: conversation.aliIdEncrypt,
|
||||
searchMessageId: "",
|
||||
timeSlide: { forward: false, timeStamp: Date.now(), pageSize: 20 },
|
||||
},
|
||||
conversation,
|
||||
);
|
||||
|
||||
return (response.list ?? []).filter(
|
||||
(message) =>
|
||||
message.messageType === "rec" &&
|
||||
message.type === 1 &&
|
||||
message.viewType === 0 &&
|
||||
message.msgType === 10010 &&
|
||||
message.subType === 50 &&
|
||||
message.originalData?.cardType === 6,
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
这个函数的返回值只应在 MAIN world 中用于归类或进一步受控解析。跨页面桥、Service Worker、IndexedDB、Bright 或 Mind 时,应传递白名单化的业务字段或明确的 `unsupported` 结果,绝不能传递整个 `message`、`content`、`params`、`chatToken` 或序列化的原始对象。
|
||||
|
||||
## 5. 已验证与未覆盖
|
||||
|
||||
- 已验证:上述判别组合、`originalData.params` 键集合、`icbuData` 字段可用性、单页 SDK 返回形态。
|
||||
- 未覆盖:询盘 `content` 的稳定文本格式、商品详情 API、详情链接生成规则、不同站点/订单状态下的变体。
|
||||
@@ -0,0 +1,130 @@
|
||||
# OneTalk 订单消息:运行态格式观察
|
||||
|
||||
> 观察日期:2026-09-11
|
||||
> 证据边界:现有已登录 OneTalk PWA 的 Chromium CDP,只读调用页面 SDK;不保存或输出原始订单、地址、令牌、加密标识、消息正文或完整 URL。
|
||||
> 性质:单个真实样本的运行态观察,不是订单系统接口契约,也不是当前跨层数据合同。
|
||||
|
||||
## 1. 怎么判断是订单
|
||||
|
||||
SDK 历史条目的订单判别条件为:
|
||||
|
||||
```ts
|
||||
const isOrderMessage = (message: Record<string, unknown>): boolean =>
|
||||
message.messageType === "rec" &&
|
||||
message.type === 1 &&
|
||||
message.viewType === 0 &&
|
||||
message.msgType === 10010 &&
|
||||
message.subType === 59 &&
|
||||
message.originalData?.cardType === 9;
|
||||
```
|
||||
|
||||
`msgType=10010` 是业务卡片族而不是订单标记;必须同时检查 `subType=59` 和 `cardType=9`。
|
||||
|
||||
## 2. JSON 中已有的数据
|
||||
|
||||
### 2.1 订单关联字段
|
||||
|
||||
```text
|
||||
originalData.cardType = 9
|
||||
originalData.params keys =
|
||||
orderId, bizCode, contractId, sign, ctime,
|
||||
from, to, id, params, tenant
|
||||
```
|
||||
|
||||
`orderId`、`contractId`、`id` 和 `bizCode` 可用于同一受控边界内的订单关联。`sign`、参与方标识和加密/令牌型字段不得跨 MAIN world 传输或持久化。
|
||||
|
||||
### 2.2 Base64 订单摘要
|
||||
|
||||
`originalData.params.params` 在本样本中是 Base64 编码的 UTF-8 JSON。解码后具有以下结构:
|
||||
|
||||
```ts
|
||||
type OrderSummary = {
|
||||
id: number;
|
||||
orderAmount: number;
|
||||
orderAmountCurrency: string;
|
||||
paymentAmount: number;
|
||||
paymentAmountCurrency: string;
|
||||
statusMessageKey: string;
|
||||
actionList: Array<{
|
||||
name: string;
|
||||
messageKey: string;
|
||||
properties: {
|
||||
payStep?: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
```
|
||||
|
||||
因此当前可直接获得:
|
||||
|
||||
- 订单和实付金额及币种;
|
||||
- 非本地化的订单状态键 `statusMessageKey`;
|
||||
- 动作列表及部分付款阶段 `payStep`;
|
||||
- 订单关联 ID。
|
||||
|
||||
### 2.3 当前不能从结构化摘要直接获得的数据
|
||||
|
||||
样本 UI 显示的商品数量、商品明细/图片、人类可读状态、收件地址和详情链接不在上述已解码摘要内。
|
||||
|
||||
本样本 `content` 为非 JSON 的普通字符串模板。它不能直接作为稳定 schema 使用,也不能未经白名单处理跨层传递。
|
||||
|
||||
`extInfo.icbuData` 中的 `title`、`iconUrl`、`cardUrls`、`actions`、`defaultContent` 在本样本为空;只有 `chatEvent` 有值,不能据此承诺订单详情链接可用。
|
||||
|
||||
## 3. 怎么获取
|
||||
|
||||
### 3.1 取得订单消息
|
||||
|
||||
```js
|
||||
const getOrderMessages = async (conversation) => {
|
||||
const service = window.IcbuIM.IMBaaSSDK.default.getMessageService();
|
||||
const response = await service.fetchMessagesWithoutUpdateToRead(
|
||||
{
|
||||
conversationCode: conversation.cid,
|
||||
contactAccountId: conversation.accountId,
|
||||
contactAccountIdEncrypt: conversation.accountIdEncrypt,
|
||||
aliId: conversation.aliId,
|
||||
aliIdEncrypt: conversation.aliIdEncrypt,
|
||||
searchMessageId: "",
|
||||
timeSlide: { forward: false, timeStamp: Date.now(), pageSize: 20 },
|
||||
},
|
||||
conversation,
|
||||
);
|
||||
|
||||
return (response.list ?? []).filter(
|
||||
(message) =>
|
||||
message.messageType === "rec" &&
|
||||
message.type === 1 &&
|
||||
message.viewType === 0 &&
|
||||
message.msgType === 10010 &&
|
||||
message.subType === 59 &&
|
||||
message.originalData?.cardType === 9,
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 3.2 在 MAIN world 内解码摘要
|
||||
|
||||
必须限制大小、验证 Base64/UTF-8/JSON 和字段 schema;示例仅展示解码入口,不构成跨层透传授权。
|
||||
|
||||
```js
|
||||
const decodeOrderSummary = (encoded) => {
|
||||
if (typeof encoded !== "string" || encoded.length === 0) return null;
|
||||
|
||||
try {
|
||||
const bytes = Uint8Array.from(atob(encoded), (character) => character.charCodeAt(0));
|
||||
const parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
||||
|
||||
if (!parsed || typeof parsed !== "object") return null;
|
||||
return parsed;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
生产实现不能将 `null` 静默当作“无订单”:Base64、UTF-8、JSON 或 schema 失败应成为可观测的显式异常/受控 `unsupported` 结果。
|
||||
|
||||
## 4. 已验证与未覆盖
|
||||
|
||||
- 已验证:`10010/59/cardType=9` 判别组合,订单参数键,嵌套 `params` 的 Base64-UTF-8-JSON 编码,以及摘要键集合。
|
||||
- 未覆盖:商品数量和明细、图片、状态键到本地化文案的映射、收件地址、详情链接、不同订单状态及多商品订单的字段变体。
|
||||
Reference in New Issue
Block a user