(msg: Api.Message, args: string[])
| 1069 | // 准备用户数据 |
| 1070 | const userId = (sender as any).id?.toString(); |
| 1071 | const name = (sender as any).name || ""; |
| 1072 | const firstName = |
| 1073 | (sender as any).firstName || (sender as any).title || ""; |
| 1074 | const lastName = (sender as any).lastName || ""; |
| 1075 | const username = (sender as any).username || ""; |
| 1076 | // teleproto may expose emojiStatus.documentId as big-int-like; also try nested raw |
| 1077 | const emojiStatus = |
| 1078 | emojiStatusIdFromEntity(sender) || |
| 1079 | (sender as any).emojiStatus?.documentId?.toString?.() || |
| 1080 | (sender as any).emoji_status?.documentId?.toString?.() || |
| 1081 | null; |
| 1082 | |
| 1083 | // 生成用户唯一标识符:优先使用 userId,如果没有则使用名称的 hashCode |
| 1084 | const currentUserIdentifier = |
| 1085 | userId || |
| 1086 | hashCode( |
| 1087 | name || `${firstName}|${lastName}` || `user_${i}`, |
| 1088 | ).toString(); |
| 1089 | |
| 1090 | // 判断是否应该显示头像:只有当前用户与上一条消息的用户不同时才显示 |
| 1091 | const shouldShowAvatar = |
| 1092 | currentUserIdentifier !== previousUserIdentifier; |
| 1093 | previousUserIdentifier = currentUserIdentifier; |
| 1094 | |
| 1095 | let photo: { url: string } | undefined = undefined; |
| 1096 | if (shouldShowAvatar) { |
| 1097 | try { |
| 1098 | const buffer = await downloadEntityAvatar(client, sender); |
| 1099 | if (Buffer.isBuffer(buffer) && buffer.length > 0) { |
| 1100 | const base64 = buffer.toString("base64"); |
| 1101 | photo = { |
| 1102 | url: `data:image/png;base64,${base64}`, |
| 1103 | }; |
| 1104 | } else { |
| 1105 | console.warn("下载的头像数据无效或用户无头像"); |
| 1106 | } |
| 1107 | } catch (e) { |
| 1108 | console.warn("下载用户头像失败", e); |
| 1109 | } |
| 1110 | // 远程 quote-api 的 emoji_status 只接受纯字符串 ID,由远端自行拉表情。 |
| 1111 | // 本地无需预下载 customEmojiBuffer。 |
| 1112 | } |
| 1113 | |
| 1114 | if (i === 0) { |
| 1115 | let replyTo = (trigger || msg)?.replyTo; |
| 1116 | if (replyTo?.quoteText) { |
| 1117 | message.message = replyTo.quoteText; |
| 1118 | message.entities = replyTo.quoteEntities; |
| 1119 | } |
| 1120 | } |
| 1121 | |
| 1122 | // 转换消息实体 |
| 1123 | const entities = convertEntities(message.entities || []); |
| 1124 | |
| 1125 | // 处理回复引用(支持 quote header 与真实被回复消息) |
| 1126 | let replyBlock: any | undefined; |
| 1127 | if (r) { |
| 1128 | try { |
no test coverage detected