mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-20 00:48:12 +00:00
feat(qq): msg_id→get_history 7天兜底 + list_chats/mark_read 会话列表 v1.2.0
## 问题
1. NapCat get_msg 的 message_id 是 QQ 服务端临时短号,约 3 天后失效。
实测 1306 条真实 webhook 消息,141 条(10.8%)现在查回报「消息不存在」,
全是 3 天前的旧消息;1 小时内的消息可正常查回。NapCat 侧无保留时长配置项
(napcat.json / onebot11_*.json / webui.json 均无),是 QQ 协议硬限制。
模型收到 not_found 后曾编造正文(虚构 message_id + 虚构需求),
已在核心 prompt 加事实性约束,此处从插件层根治取不到正文的问题。
2. 插件与真人客户端差距大:没有会话列表、消息不按到达先后排序、无未读提醒,
模型只能靠单条中断消息被动响应,导致消息处理不及时。
## 改动
### msg_id → get_history 兜底(不缓存正文)
- 新增 msgRef{peerID,isGroup,time}:只记 msg_id → (peer, 时间) 映射,7 天 TTL
(qqMsgTTL),超 2000 条时惰性清理过期项。不缓存消息正文。
- handleGetMessage 改为包装层:命中映射且 <7 天 → getMsgFromHistoryByTime()
按 peer 拉 get_history(count=50),取时间最接近的一条,
经 msgToGetMsgResult() 包装为与 get_msg 同构的结果(附 resolved_via:history);
未命中或超 7 天 → 回退原 NapCat get_msg(重命名为 getMsgFromNapcat)。
- 超 7 天的消息由 get_history 自行处理,插件不做长期缓存。
- not_found 文案改为明确引导改用 qq_get_history / qq_list_chats。
### 会话列表(对齐真人客户端)
- 新增 chatMeta:会话名、未读数、最新一条 ≤60 字摘要(qqLastSumLen)、最新时间。
只维护最新一条摘要,不存历史。
- 新增 qq_list_chats:按最新消息时间降序返回会话列表,每项含
peer_id/type/name/unread/last_text/last_nick/last_time。
- 新增 qq_mark_read:按 group_id/user_id 清零未读;get_history 拉取某会话后
自动标已读(看过=已读,与真人客户端一致)。
- webhook 记录时机前移:策略允许的消息(群/私聊、是否 @bot 均记)都进入映射与
会话状态,@bot 只决定是否发中断——与真人客户端一致能看到全部会话。
### 中断模板
- 补 fallback 路径与私聊 user_id(原模板只给 message_id,取不到正文时
模型没有 peer 信息可用于 get_history):
「先用 get_message 取正文;若取不到(已过期),改用 get_history(...) 按会话拉取,
或用 list_chats 查看未读会话。用 output_send__qq 回复」
## 验证
用重建的 plugindev build --target linux/amd64 产出 dist/qq_linux_amd64.hmap,
经内核 plugin_install(overwrite=true) 安装(action=reinstalled, config_kept=true),
重启 homed 后内核注册 20 个 qq_* 工具(原 18 + list_chats + mark_read)。
实测 qq_list_chats(count=8) 返回按时间排序的会话,unread=6 正确累积。
注意:cgo c-shared 插件带完整 Go runtime,dlclose 后引用计数不归零,
同路径 dlopen 复用旧映像,plgreload 无法热替换 .so,换 .so 必须重启 homed。
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
"name": "qq",
|
||||
"name_zh": "QQ消息",
|
||||
"name_en": "qq",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"description": "QQ 消息收发插件,通过 NapCat 协议桥接",
|
||||
"author": "HomeAgent",
|
||||
"entry": "plugin.so",
|
||||
|
||||
@ -107,13 +107,149 @@ type Plugin struct {
|
||||
downloadTasks []*DownloadTask
|
||||
typingMu sync.Mutex
|
||||
typingMap map[int64]*typingState
|
||||
|
||||
// msg_id → peer 映射 + 会话最新状态(<7 天兜底 get_history + list_chats)
|
||||
msgMu sync.Mutex
|
||||
msgMap map[int64]msgRef // message_id → {peer, time}
|
||||
chats map[int64]*chatMeta // peerID → 会话状态(群号或 QQ 号)
|
||||
}
|
||||
|
||||
|
||||
type typingState struct {
|
||||
userID int64
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
// msgRef 一条已见过的消息的引用:只记录 msg_id → (peer, time) 映射,不缓存正文。
|
||||
// 用途:NapCat get_msg 的临时短号 <7 天失效时,据此把 get_msg 兜底为按 peer 拉 get_history。
|
||||
type msgRef struct {
|
||||
peerID int64
|
||||
isGroup bool
|
||||
time int64 // 秒级时间戳
|
||||
}
|
||||
|
||||
// chatMeta 一个会话(群/私聊)的最新状态,供 list_chats 展示。
|
||||
// 只维护最新一条的短摘要(≤qqLastSumLen 字符)与未读数,不缓存完整历史。
|
||||
type chatMeta struct {
|
||||
peerID int64
|
||||
isGroup bool
|
||||
name string
|
||||
unread int
|
||||
lastTime int64
|
||||
lastText string
|
||||
lastNick string
|
||||
}
|
||||
|
||||
const qqMsgTTL = 7 * 86400 // 7 天:msg_id → peer 映射的有效期
|
||||
const qqLastSumLen = 60 // list_chats 里最新一条摘要的最大长度
|
||||
|
||||
// snapshotMsg 记录一条策略允许的消息:更新 msg_id→peer 映射与会话未读/最新状态。
|
||||
// 不缓存消息正文(仅最新一条留 ≤qqLastSumLen 的摘要供列表展示)。
|
||||
func (p *Plugin) snapshotMsg(msgID, peerID int64, isGroup bool, t int64, nickname, text string) {
|
||||
if msgID <= 0 {
|
||||
return
|
||||
}
|
||||
p.msgMu.Lock()
|
||||
defer p.msgMu.Unlock()
|
||||
|
||||
// msg_id 映射(7 天 TTL,惰性清理)
|
||||
p.msgMap[msgID] = msgRef{peerID: peerID, isGroup: isGroup, time: t}
|
||||
now := time.Now().Unix()
|
||||
if len(p.msgMap) > 2000 { // 定期清理过期项
|
||||
for k, v := range p.msgMap {
|
||||
if now-v.time > qqMsgTTL {
|
||||
delete(p.msgMap, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ch := p.chats[peerID]
|
||||
if ch == nil {
|
||||
ch = &chatMeta{peerID: peerID, isGroup: isGroup}
|
||||
p.chats[peerID] = ch
|
||||
}
|
||||
if ch.name == "" {
|
||||
if isGroup {
|
||||
ch.name = fmt.Sprintf("群%d", peerID)
|
||||
} else {
|
||||
ch.name = nickname
|
||||
}
|
||||
}
|
||||
// 按到达次序维护未读与最新摘要:仅当本条更新时才更新 lastTime/lastText(保持按时间排)
|
||||
if t > ch.lastTime {
|
||||
ch.lastTime = t
|
||||
ch.lastText = text
|
||||
ch.lastNick = nickname
|
||||
}
|
||||
ch.unread++
|
||||
}
|
||||
|
||||
// lookupMsgRef 查 msg_id 映射,返回 (peer, isGroup, time, ok)。超过 7 天视为无效(交给 get_history)。
|
||||
func (p *Plugin) lookupMsgRef(msgID int64) (int64, bool, int64, bool) {
|
||||
p.msgMu.Lock()
|
||||
defer p.msgMu.Unlock()
|
||||
ref, ok := p.msgMap[msgID]
|
||||
if !ok {
|
||||
return 0, false, 0, false
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
if now-ref.time > qqMsgTTL {
|
||||
delete(p.msgMap, msgID)
|
||||
return 0, false, 0, false
|
||||
}
|
||||
return ref.peerID, ref.isGroup, ref.time, true
|
||||
}
|
||||
|
||||
// markChatRead 清零某会话未读数(模型处理完该会话后调用)。
|
||||
func (p *Plugin) markChatRead(peerID int64) {
|
||||
p.msgMu.Lock()
|
||||
defer p.msgMu.Unlock()
|
||||
if ch := p.chats[peerID]; ch != nil {
|
||||
ch.unread = 0
|
||||
}
|
||||
}
|
||||
|
||||
// listChats 返回会话列表:按最新消息时间降序,含未读数与最新一条摘要。
|
||||
func (p *Plugin) listChats(capN int) []map[string]interface{} {
|
||||
p.msgMu.Lock()
|
||||
list := make([]*chatMeta, 0, len(p.chats))
|
||||
for _, c := range p.chats {
|
||||
list = append(list, c)
|
||||
}
|
||||
p.msgMu.Unlock()
|
||||
|
||||
// 降序(最新消息在前)
|
||||
for i := 1; i < len(list); i++ {
|
||||
for j := i; j > 0 && list[j].lastTime > list[j-1].lastTime; j-- {
|
||||
list[j], list[j-1] = list[j-1], list[j]
|
||||
}
|
||||
}
|
||||
if len(list) > capN {
|
||||
list = list[:capN]
|
||||
}
|
||||
|
||||
out := make([]map[string]interface{}, 0, len(list))
|
||||
for _, c := range list {
|
||||
typ := "private"
|
||||
if c.isGroup {
|
||||
typ = "group"
|
||||
}
|
||||
item := map[string]interface{}{
|
||||
"peer_id": c.peerID,
|
||||
"type": typ,
|
||||
"name": c.name,
|
||||
"unread": c.unread,
|
||||
"last_text": c.lastText,
|
||||
"last_nick": c.lastNick,
|
||||
}
|
||||
if c.lastTime > 0 {
|
||||
item["last_time"] = time.Unix(c.lastTime, 0).Format("2006-01-02 15:04")
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (p *Plugin) Name() string { return p.name }
|
||||
|
||||
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||
@ -150,6 +286,10 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||
|
||||
p.httpClient = &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
// msg_id → peer 映射 + 会话状态(不缓存正文)
|
||||
p.msgMap = make(map[int64]msgRef)
|
||||
p.chats = make(map[int64]*chatMeta)
|
||||
|
||||
// 从 NapCat 获取 Bot 身份(阻塞等待,最多 5s)
|
||||
p.fetchBotInfo()
|
||||
if p.botID == 0 {
|
||||
@ -248,6 +388,27 @@ type 枚举: text(文字)/ voice(语音转文字后发送)/ image(图
|
||||
},
|
||||
}, p.handleGetHistory)
|
||||
|
||||
p.regTool(s, sdk.ToolDef{
|
||||
Name: tp + "list_chats", Description: "获取QQ会话列表,与真人客户端一致:按最新消息先后排序,每条标注会话(群/私聊)、会话名、未读消息数、最新一条消息摘要与时间。用于发现有未读消息的会话,再配合 qq_get_history 拉取对应会话内容、output_send__qq 回复。",
|
||||
NoMemory: false,
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object", "properties": map[string]interface{}{
|
||||
"count": map[string]interface{}{"type": "integer", "description": "最多返回会话数,默认10"},
|
||||
}, "required": []string{},
|
||||
},
|
||||
}, p.handleListChats)
|
||||
|
||||
p.regTool(s, sdk.ToolDef{
|
||||
Name: tp + "mark_read", Description: "将某个会话的未读计数清零(对象:群聊传 group_id,私聊传 user_id)。处理完某会话消息后可调用,让 list_chats 的未读数回到0,与真人客户端标记已读一致。",
|
||||
NoMemory: false,
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object", "properties": map[string]interface{}{
|
||||
"group_id": map[string]interface{}{"type": "integer", "description": "群号(与user_id二选一)"},
|
||||
"user_id": map[string]interface{}{"type": "integer", "description": "QQ号(与group_id二选一)"},
|
||||
}, "required": []string{},
|
||||
},
|
||||
}, p.handleMarkRead)
|
||||
|
||||
// ---- 查询 ----
|
||||
p.regTool(s, sdk.ToolDef{
|
||||
Name: tp + "get_groups", Description: "获取QQ群列表,可按关键词搜索群名",
|
||||
@ -772,6 +933,28 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 记录 msg_id→peer 映射与会话状态(不缓存正文,仅最新一条短摘要)----
|
||||
// 策略允许的消息(群/私聊、是否 @bot 均记),供 get_msg 兜底与 list_chats 使用;
|
||||
// @bot 与否只决定是否发中断,不影响记录——与真人客户端一致看到全部会话。
|
||||
{
|
||||
peerID, isGroup := evt.UserID, false
|
||||
if evt.MessageType == "group" {
|
||||
peerID, isGroup = evt.GroupID, true
|
||||
}
|
||||
sum := text
|
||||
runes := []rune(sum)
|
||||
if len(runes) > qqLastSumLen {
|
||||
sum = string(runes[:qqLastSumLen]) + "…"
|
||||
}
|
||||
if evt.Time == 0 {
|
||||
evt.Time = time.Now().Unix()
|
||||
}
|
||||
p.snapshotMsg(evt.MessageID, peerID, isGroup, evt.Time, nickname, sum)
|
||||
}
|
||||
|
||||
if evt.MessageType == "group" {
|
||||
// 群消息必须 @ 机器人才响应
|
||||
if p.botID == 0 {
|
||||
log.Printf("[qq] bot ID unknown, rejecting group message from %d", evt.GroupID)
|
||||
@ -791,9 +974,9 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
outputTool := "output_send__" + p.name
|
||||
var interrupt string
|
||||
if evt.MessageType == "group" {
|
||||
interrupt = fmt.Sprintf("来自「%s」在群「%s」的消息(message_id=%d)。使用%sget_message(message_id=%d)获取消息正文。如果消息包含引用回复,使用%sget_history(group_id=%d)查看上下文。使用%s回复群聊", nickname, "群聊", evt.MessageID, tp, evt.MessageID, tp, evt.GroupID, outputTool)
|
||||
interrupt = fmt.Sprintf("来自「%s」在群「%s」的消息(message_id=%d)。先用%sget_message(message_id=%d)取正文;若取不到(消息已过期),改用%sget_history(group_id=%d)按会话拉取上下文,或用%slist_chats 查看未读会话。用%s回复群聊", nickname, "群聊", evt.MessageID, tp, evt.MessageID, tp, evt.GroupID, tp, outputTool)
|
||||
} else {
|
||||
interrupt = fmt.Sprintf("来自「%s」的私聊消息(message_id=%d)。使用%sget_message(message_id=%d)获取消息正文。使用%s回复对方", nickname, evt.MessageID, tp, evt.MessageID, outputTool)
|
||||
interrupt = fmt.Sprintf("来自「%s」的私聊消息(message_id=%d, user_id=%d)。先用%sget_message(message_id=%d)取正文;若取不到(消息已过期),改用%sget_history(user_id=%d)按会话拉取上下文,或用%slist_chats 查看未读会话。用%s回复对方", nickname, evt.MessageID, evt.UserID, tp, evt.MessageID, tp, evt.UserID, tp, outputTool)
|
||||
}
|
||||
if p.isAdmin(evt.UserID) {
|
||||
interrupt = "【重要!老大消息】" + interrupt
|
||||
@ -841,6 +1024,126 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// ======== Tool Handlers ========
|
||||
|
||||
// getMsgFromHistoryByTime 按 (peer, isGroup, targetTime) 从 NapCat 拉最近历史,返回距 targetTime 最近的完整消息。
|
||||
func (p *Plugin) getMsgFromHistoryByTime(peerID int64, isGroup bool, targetTime int64) (map[string]interface{}, bool) {
|
||||
ep := "get_friend_msg_history"
|
||||
params := map[string]interface{}{"user_id": peerID, "count": 50}
|
||||
if isGroup {
|
||||
ep = "get_group_msg_history"
|
||||
params = map[string]interface{}{"group_id": peerID, "count": 50}
|
||||
}
|
||||
raw, err := p.napcat(ep, params)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
rawStr, _ := rawString(raw)
|
||||
if rawStr == "" {
|
||||
return nil, false
|
||||
}
|
||||
var resp struct {
|
||||
Data *struct {
|
||||
Messages []interface{} `json:"messages"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if json.Unmarshal([]byte(rawStr), &resp) != nil || resp.Data == nil {
|
||||
return nil, false
|
||||
}
|
||||
var best map[string]interface{}
|
||||
bestAbs := int64(-1)
|
||||
for _, m := range resp.Data.Messages {
|
||||
mm, ok := m.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
mt, _ := mm["time"].(float64)
|
||||
t := int64(mt)
|
||||
if t == 0 {
|
||||
continue
|
||||
}
|
||||
abs := t - targetTime
|
||||
if abs < 0 {
|
||||
abs = -abs
|
||||
}
|
||||
if bestAbs < 0 || abs < bestAbs {
|
||||
bestAbs = abs
|
||||
best = mm
|
||||
}
|
||||
}
|
||||
if best == nil {
|
||||
return nil, false
|
||||
}
|
||||
return best, true
|
||||
}
|
||||
|
||||
// msgToGetMsgResult 把一条 NapCat 历史消息对象转成与 get_msg 同构的结果(历史包装语义)。
|
||||
func msgToGetMsgResult(msg map[string]interface{}) map[string]interface{} {
|
||||
nickname := ""
|
||||
if s, ok := msg["sender"].(map[string]interface{}); ok {
|
||||
if n, _ := s["nickname"].(string); n != "" {
|
||||
nickname = n
|
||||
}
|
||||
if c, _ := s["card"].(string); c != "" {
|
||||
nickname = c
|
||||
}
|
||||
}
|
||||
rawText, _ := msg["raw_message"].(string)
|
||||
content := rawText
|
||||
if content == "" {
|
||||
if segs, ok := msg["message"].([]interface{}); ok {
|
||||
var parts []string
|
||||
for _, seg := range segs {
|
||||
segMap, _ := seg.(map[string]interface{})
|
||||
if segMap == nil {
|
||||
continue
|
||||
}
|
||||
typ, _ := segMap["type"].(string)
|
||||
segData, _ := segMap["data"].(map[string]interface{})
|
||||
if segData == nil {
|
||||
continue
|
||||
}
|
||||
switch typ {
|
||||
case "text":
|
||||
if t, _ := segData["text"].(string); t != "" {
|
||||
parts = append(parts, t)
|
||||
}
|
||||
case "image":
|
||||
parts = append(parts, "[图片]")
|
||||
case "file":
|
||||
if n, _ := segData["name"].(string); n != "" {
|
||||
parts = append(parts, "[文件:"+n+"]")
|
||||
}
|
||||
default:
|
||||
if typ != "" {
|
||||
parts = append(parts, "["+typ+"]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(parts) > 0 {
|
||||
content = strings.Join(parts, " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
mid, _ := msg["message_id"].(float64)
|
||||
uid, _ := msg["user_id"].(float64)
|
||||
gid, _ := msg["group_id"].(float64)
|
||||
mt, _ := msg["time"].(float64)
|
||||
mtType, _ := msg["message_type"].(string)
|
||||
loc := "私聊"
|
||||
if mtType == "group" || gid > 0 {
|
||||
loc = "群聊"
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"content": content,
|
||||
"message_id": int64(mid),
|
||||
"user_id": int64(uid),
|
||||
"group_id": int64(gid),
|
||||
"nickname": nickname,
|
||||
"message_type": mtType,
|
||||
"type": loc,
|
||||
"time": time.Unix(int64(mt), 0).Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Plugin) handleGetMessage(args map[string]interface{}) (interface{}, error) {
|
||||
msgID, err := convInt64(args["message_id"])
|
||||
if err != nil {
|
||||
@ -850,10 +1153,24 @@ func (p *Plugin) handleGetMessage(args map[string]interface{}) (interface{}, err
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 本地 msg_id→peer 映射命中且 <7 天 → 用 get_history 语义兜底(NapCat 临时短号失效也不怕)
|
||||
if peerID, isGroup, t, ok := p.lookupMsgRef(msgID); ok {
|
||||
if m, found := p.getMsgFromHistoryByTime(peerID, isGroup, t); found {
|
||||
// 找到同会话、时间最接近的消息,包装为 get_msg 同构返回
|
||||
res := msgToGetMsgResult(m)
|
||||
res["resolved_via"] = "history" // 标明由历史查询兜底
|
||||
return res, nil
|
||||
}
|
||||
// 历史窗口内没找到(消息可能被裁剪/更早),回退 NapCat 原查询
|
||||
}
|
||||
return p.getMsgFromNapcat(msgID)
|
||||
}
|
||||
|
||||
func (p *Plugin) getMsgFromNapcat(msgID int64) (interface{}, error) {
|
||||
raw, err := p.napcat("get_msg", map[string]interface{}{"message_id": msgID})
|
||||
if err != nil {
|
||||
return map[string]interface{}{
|
||||
"content": fmt.Sprintf("查询 NapCat 失败: %s", err),
|
||||
"content": fmt.Sprintf("查询 NapCat 失败: %s。该 message_id 可能已过期,请改用 qq_get_history 按会话拉取最近消息(或用 qq_list_chats 看未读会话)", err),
|
||||
"message_id": msgID,
|
||||
"not_found": true,
|
||||
}, nil
|
||||
@ -884,7 +1201,7 @@ func (p *Plugin) handleGetMessage(args map[string]interface{}) (interface{}, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(rawStr), &resp); err != nil || resp.Data == nil {
|
||||
return map[string]interface{}{
|
||||
"content": "解析 NapCat 响应失败",
|
||||
"content": "解析 NapCat 响应失败(消息可能已过期)。请改用 qq_get_history 按会话拉取最近消息,或用 qq_list_chats 查看未读会话",
|
||||
"message_id": msgID,
|
||||
"not_found": true,
|
||||
}, nil
|
||||
@ -1214,6 +1531,31 @@ func (p *Plugin) handleSendFile(args map[string]interface{}) (interface{}, error
|
||||
return p.napcat("send_private_msg", params)
|
||||
}
|
||||
|
||||
func (p *Plugin) handleListChats(args map[string]interface{}) (interface{}, error) {
|
||||
count := 10
|
||||
if c, err := convInt64(args["count"]); err == nil && c > 0 && c < 100 {
|
||||
count = int(c)
|
||||
}
|
||||
chats := p.listChats(count)
|
||||
return map[string]interface{}{
|
||||
"chats": chats,
|
||||
"total": len(chats),
|
||||
"hint": "按最新消息先后排序;unread 为该会话未读消息数,处理完用 qq_mark_read 清零;用 qq_get_history(group_id/user_id) 拉取会话内容",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Plugin) handleMarkRead(args map[string]interface{}) (interface{}, error) {
|
||||
if gid, err := convInt64(args["group_id"]); err == nil {
|
||||
p.markChatRead(gid)
|
||||
return map[string]interface{}{"status": "ok", "group_id": gid, "unread": 0}, nil
|
||||
}
|
||||
if uid, err := convInt64(args["user_id"]); err == nil {
|
||||
p.markChatRead(uid)
|
||||
return map[string]interface{}{"status": "ok", "user_id": uid, "unread": 0}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("need group_id or user_id")
|
||||
}
|
||||
|
||||
func (p *Plugin) handleGetHistory(args map[string]interface{}) (interface{}, error) {
|
||||
gid, gerr := convInt64(args["group_id"])
|
||||
uid, uerr := convInt64(args["user_id"])
|
||||
@ -1352,6 +1694,12 @@ func (p *Plugin) handleGetHistory(args map[string]interface{}) (interface{}, err
|
||||
if len(files) > 0 {
|
||||
result["files"] = files
|
||||
}
|
||||
// 拉取过某会话历史即视为已读(与真人客户端一致:看过=已读)
|
||||
if gerr == nil {
|
||||
p.markChatRead(gid)
|
||||
} else if uerr == nil {
|
||||
p.markChatRead(uid)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user