// Copyright © 2023 OpenIM SDK. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package conversation_msg import ( "context" "errors" _ "github.com/openimsdk/openim-sdk-core/v3/internal/common" "github.com/openimsdk/openim-sdk-core/v3/internal/util" "github.com/openimsdk/openim-sdk-core/v3/pkg/common" "github.com/openimsdk/openim-sdk-core/v3/pkg/constant" "github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" sdk "github.com/openimsdk/openim-sdk-core/v3/pkg/sdk_params_callback" "github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs" "github.com/openimsdk/openim-sdk-core/v3/pkg/server_api_params" "github.com/openimsdk/openim-sdk-core/v3/pkg/utils" "github.com/openimsdk/openim-sdk-core/v3/sdk_struct" "github.com/openimsdk/tools/utils/datautil" "sort" "strings" "time" "github.com/jinzhu/copier" "github.com/openimsdk/tools/log" "github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/sdkws" pbConversation "github.com/openimsdk/protocol/conversation" ) func (c *Conversation) setConversation(ctx context.Context, apiReq *pbConversation.SetConversationsReq, localConversation *model_struct.LocalConversation) error { apiReq.Conversation.ConversationID = localConversation.ConversationID apiReq.Conversation.ConversationType = localConversation.ConversationType apiReq.Conversation.UserID = localConversation.UserID apiReq.Conversation.GroupID = localConversation.GroupID apiReq.UserIDs = []string{c.loginUserID} if err := util.ApiPost(ctx, constant.SetConversationsRouter, apiReq, nil); err != nil { return err } return nil } func (c *Conversation) getServerConversationList(ctx context.Context) ([]*model_struct.LocalConversation, error) { resp, err := util.CallApi[pbConversation.GetAllConversationsResp](ctx, constant.GetAllConversationsRouter, pbConversation.GetAllConversationsReq{OwnerUserID: c.loginUserID}) if err != nil { return nil, err } return datautil.Batch(ServerConversationToLocal, resp.Conversations), nil } func (c *Conversation) getServerConversationsByIDs(ctx context.Context, conversations []string) ([]*model_struct.LocalConversation, error) { resp, err := util.CallApi[pbConversation.GetConversationsResp](ctx, constant.GetConversationsRouter, pbConversation.GetConversationsReq{OwnerUserID: c.loginUserID, ConversationIDs: conversations}) if err != nil { return nil, err } return datautil.Batch(ServerConversationToLocal, resp.Conversations), nil } func (c *Conversation) getServerHasReadAndMaxSeqs(ctx context.Context, conversationIDs ...string) (map[string]*msg.Seqs, error) { resp := &msg.GetConversationsHasReadAndMaxSeqResp{} req := msg.GetConversationsHasReadAndMaxSeqReq{UserID: c.loginUserID} req.ConversationIDs = conversationIDs err := util.ApiPost(ctx, constant.GetConversationsHasReadAndMaxSeqRouter, &req, resp) if err != nil { log.ZError(ctx, "getServerHasReadAndMaxSeqs err", err) return nil, err } return resp.Seqs, nil } func (c *Conversation) getAdvancedHistoryMessageList(ctx context.Context, req sdk.GetAdvancedHistoryMessageListParams, isReverse bool) (*sdk.GetAdvancedHistoryMessageListCallback, error) { t := time.Now() var messageListCallback sdk.GetAdvancedHistoryMessageListCallback var conversationID string var startTime int64 var sessionType int var list []*model_struct.LocalChatLog var err error var messageList sdk_struct.NewMsgList var notStartTime bool conversationID = req.ConversationID lc, err := c.db.GetConversation(ctx, conversationID) if err != nil { return nil, err } sessionType = int(lc.ConversationType) if req.StartClientMsgID == "" { notStartTime = true } else { m, err := c.db.GetMessage(ctx, conversationID, req.StartClientMsgID) if err != nil { return nil, err } startTime = m.SendTime } log.ZDebug(ctx, "Assembly conversation parameters", "cost time", time.Since(t), "conversationID", conversationID, "startTime:", startTime, "count:", req.Count, "not start_time", notStartTime) t = time.Now() if notStartTime { list, err = c.db.GetMessageListNoTime(ctx, conversationID, req.Count, isReverse) } else { list, err = c.db.GetMessageList(ctx, conversationID, req.Count, startTime, isReverse) } log.ZDebug(ctx, "db get messageList", "cost time", time.Since(t), "len", len(list), "err", err, "conversationID", conversationID) if err != nil { return nil, err } rawMessageLength := len(list) t = time.Now() if rawMessageLength < req.Count { maxSeq, minSeq, lostSeqListLength := c.messageBlocksInternalContinuityCheck(ctx, conversationID, notStartTime, isReverse, req.Count, startTime, &list, &messageListCallback) _ = c.messageBlocksBetweenContinuityCheck(ctx, req.LastMinSeq, maxSeq, conversationID, notStartTime, isReverse, req.Count, startTime, &list, &messageListCallback) if minSeq == 1 && lostSeqListLength == 0 { messageListCallback.IsEnd = true } else { c.messageBlocksEndContinuityCheck(ctx, minSeq, conversationID, notStartTime, isReverse, req.Count, startTime, &list, &messageListCallback) } } else { maxSeq, _, _ := c.messageBlocksInternalContinuityCheck(ctx, conversationID, notStartTime, isReverse, req.Count, startTime, &list, &messageListCallback) c.messageBlocksBetweenContinuityCheck(ctx, req.LastMinSeq, maxSeq, conversationID, notStartTime, isReverse, req.Count, startTime, &list, &messageListCallback) } log.ZDebug(ctx, "pull message", "pull cost time", time.Since(t)) t = time.Now() var thisMinSeq int64 for _, v := range list { if v.Seq != 0 && thisMinSeq == 0 { thisMinSeq = v.Seq } if v.Seq < thisMinSeq && v.Seq != 0 { thisMinSeq = v.Seq } if v.Status >= constant.MsgStatusHasDeleted { log.ZDebug(ctx, "this message has been deleted or exception message", "msg", v) continue } temp := sdk_struct.MsgStruct{} temp.ClientMsgID = v.ClientMsgID temp.ServerMsgID = v.ServerMsgID temp.CreateTime = v.CreateTime temp.SendTime = v.SendTime temp.SessionType = v.SessionType temp.SendID = v.SendID temp.RecvID = v.RecvID temp.MsgFrom = v.MsgFrom temp.ContentType = v.ContentType temp.SenderPlatformID = v.SenderPlatformID temp.SenderNickname = v.SenderNickname temp.SenderFaceURL = v.SenderFaceURL temp.Content = v.Content temp.Seq = v.Seq temp.IsRead = v.IsRead temp.Status = v.Status var attachedInfo sdk_struct.AttachedInfoElem _ = utils.JsonStringToStruct(v.AttachedInfo, &attachedInfo) temp.AttachedInfoElem = &attachedInfo temp.Ex = v.Ex temp.LocalEx = v.LocalEx err := c.msgHandleByContentType(&temp) if err != nil { log.ZError(ctx, "Parsing data error", err, "temp", temp) continue } switch sessionType { case constant.GroupChatType: fallthrough case constant.SuperGroupChatType: temp.GroupID = temp.RecvID temp.RecvID = c.loginUserID } if attachedInfo.IsPrivateChat && temp.SendTime+int64(attachedInfo.BurnDuration) < time.Now().Unix() { continue } messageList = append(messageList, &temp) } log.ZDebug(ctx, "message convert and unmarshal", "unmarshal cost time", time.Since(t)) t = time.Now() if !isReverse { sort.Sort(messageList) } log.ZDebug(ctx, "sort", "sort cost time", time.Since(t)) messageListCallback.MessageList = messageList if thisMinSeq == 0 { thisMinSeq = req.LastMinSeq } messageListCallback.LastMinSeq = thisMinSeq return &messageListCallback, nil } func (c *Conversation) typingStatusUpdate(ctx context.Context, recvID, msgTip string) error { if recvID == "" { return sdkerrs.ErrArgs } s := sdk_struct.MsgStruct{} err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Typing) if err != nil { return err } s.RecvID = recvID s.SessionType = constant.SingleChatType typingElem := sdk_struct.TypingElem{} typingElem.MsgTips = msgTip s.Content = utils.StructToJsonString(typingElem) options := make(map[string]bool, 6) utils.SetSwitchFromOptions(options, constant.IsHistory, false) utils.SetSwitchFromOptions(options, constant.IsPersistent, false) utils.SetSwitchFromOptions(options, constant.IsSenderSync, false) utils.SetSwitchFromOptions(options, constant.IsConversationUpdate, false) utils.SetSwitchFromOptions(options, constant.IsSenderConversationUpdate, false) utils.SetSwitchFromOptions(options, constant.IsUnreadCount, false) utils.SetSwitchFromOptions(options, constant.IsOfflinePush, false) var wsMsgData sdkws.MsgData copier.Copy(&wsMsgData, s) wsMsgData.Content = []byte(s.Content) wsMsgData.CreateTime = s.CreateTime wsMsgData.Options = options var sendMsgResp sdkws.UserSendMsgResp err = c.LongConnMgr.SendReqWaitResp(ctx, &wsMsgData, constant.SendMsg, &sendMsgResp) if err != nil { log.ZError(ctx, "send msg to server failed", err, "message", s) return err } return nil } // funcation (c *Conversation) markMessageAsReadByConID(callback open_im_sdk_callback.Base, msgIDList sdk.MarkMessageAsReadByConIDParams, conversationID, operationID string) { // var localMessage db.LocalChatLog // var newMessageIDList []string // messages, err := c.db.GetMultipleMessage(msgIDList) // common.CheckDBErrCallback(callback, err, operationID) // for _, v := range messages { // if v.IsRead == false && v.ContentType < constant.NotificationBegin && v.SendID != c.loginUserID { // newMessageIDList = append(newMessageIDList, v.ClientMsgID) // } // } // if len(newMessageIDList) == 0 { // common.CheckAnyErrCallback(callback, 201, errors.New("message has been marked read or sender is yourself"), operationID) // } // conversationID := c.getConversationIDBySessionType(userID, constant.SingleChatType) // s := sdk_struct.MsgStruct{} // c.initBasicInfo(&s, constant.UserMsgType, constant.HasReadReceipt, operationID) // s.Content = utils.StructToJsonString(newMessageIDList) // options := make(map[string]bool, 5) // utils.SetSwitchFromOptions(options, constant.IsConversationUpdate, false) // utils.SetSwitchFromOptions(options, constant.IsSenderConversationUpdate, false) // utils.SetSwitchFromOptions(options, constant.IsUnreadCount, false) // utils.SetSwitchFromOptions(options, constant.IsOfflinePush, false) // //If there is an error, the coroutine ends, so judgment is not required // resp, _ := c.InternalSendMessage(callback, &s, userID, "", operationID, &server_api_params.OfflinePushInfo{}, false, options) // s.ServerMsgID = resp.ServerMsgID // s.SendTime = resp.SendTime // s.Status = constant.MsgStatusFiltered // msgStructToLocalChatLog(&localMessage, &s) // err = c.db.InsertMessage(&localMessage) // if err != nil { // log.Error(operationID, "inset into chat log err", localMessage, s, err.Error()) // } // err2 := c.db.UpdateMessageHasRead(userID, newMessageIDList, constant.SingleChatType) // if err2 != nil { // log.Error(operationID, "update message has read error", newMessageIDList, userID, err2.Error()) // } // _ = common.TriggerCmdUpdateConversation(common.UpdateConNode{ConID: conversationID, Action: constant.UpdateLatestMessageChange}, c.ch) // //_ = common.TriggerCmdUpdateConversation(common.UpdateConNode{ConID: conversationID, Action: constant.ConChange, Args: []string{conversationID}}, c.ch) // } func (c *Conversation) insertMessageToLocalStorage(ctx context.Context, conversationID string, s *model_struct.LocalChatLog) error { return c.db.InsertMessage(ctx, conversationID, s) } func (c *Conversation) judgeMultipleSubString(keywordList []string, main string, keywordListMatchType int) bool { if len(keywordList) == 0 { return true } if keywordListMatchType == constant.KeywordMatchOr { for _, v := range keywordList { if utils.KMP(main, v) { return true } } return false } else { for _, v := range keywordList { if !utils.KMP(main, v) { return false } } } return true } // searchLocalMessages searches for local messages based on the given search parameters. func (c *Conversation) searchLocalMessages(ctx context.Context, searchParam *sdk.SearchLocalMessagesParams) (*sdk.SearchLocalMessagesCallback, error) { var r sdk.SearchLocalMessagesCallback // Initialize the result structure var startTime, endTime int64 // Variables to hold start and end times for the search var list []*model_struct.LocalChatLog // Slice to store the search results conversationMap := make(map[string]*sdk.SearchByConversationResult, 10) // Map to store results grouped by conversation, with initial capacity of 10 var err error // Variable to store any errors encountered var conversationID string // Variable to store the current conversation ID // Set the end time for the search; if SearchTimePosition is 0, use the current timestamp if searchParam.SearchTimePosition == 0 { endTime = utils.GetCurrentTimestampBySecond() } else { endTime = searchParam.SearchTimePosition } // Set the start time based on the specified time period if searchParam.SearchTimePeriod != 0 { startTime = endTime - searchParam.SearchTimePeriod } // Convert start and end times to milliseconds startTime = utils.UnixSecondToTime(startTime).UnixNano() / 1e6 endTime = utils.UnixSecondToTime(endTime).UnixNano() / 1e6 // Validate that either keyword list or message type list is provided if len(searchParam.KeywordList) == 0 && len(searchParam.MessageTypeList) == 0 { return nil, errors.New("keywordlist and messageTypelist all null") } // Search in a specific conversation if ConversationID is provided if searchParam.ConversationID != "" { // Validate pagination parameters if searchParam.PageIndex < 1 || searchParam.Count < 1 { return nil, errors.New("page or count is null") } offset := (searchParam.PageIndex - 1) * searchParam.Count _, err := c.db.GetConversation(ctx, searchParam.ConversationID) if err != nil { return nil, err } // Search by content type or keyword based on provided parameters if len(searchParam.MessageTypeList) != 0 && len(searchParam.KeywordList) == 0 { list, err = c.db.SearchMessageByContentType(ctx, searchParam.MessageTypeList, searchParam.ConversationID, startTime, endTime, offset, searchParam.Count) } else { newContentTypeList := func(list []int) (result []int) { for _, v := range list { if utils.IsContainInt(v, SearchContentType) { result = append(result, v) } } return result }(searchParam.MessageTypeList) if len(newContentTypeList) == 0 { newContentTypeList = SearchContentType } list, err = c.db.SearchMessageByKeyword(ctx, newContentTypeList, searchParam.KeywordList, searchParam.KeywordListMatchType, searchParam.ConversationID, startTime, endTime, offset, searchParam.Count) } } else { // Comprehensive search across all conversations if len(searchParam.MessageTypeList) == 0 { searchParam.MessageTypeList = SearchContentType } list, err = c.messageController.SearchMessageByContentTypeAndKeyword(ctx, searchParam.MessageTypeList, searchParam.KeywordList, searchParam.KeywordListMatchType, startTime, endTime) } // Handle any errors encountered during the search if err != nil { return nil, err } // Logging and processing each message in the search results //localChatLogToMsgStruct(&messageList, list) //log.Debug("hahh",utils.KMP("SSSsdf3434","s")) //log.Debug("hahh",utils.KMP("SSSsdf3434","g")) //log.Debug("hahh",utils.KMP("SSSsdf3434","3434")) //log.Debug("hahh",utils.KMP("SSSsdf3434","F3434")) //log.Debug("hahh",utils.KMP("SSSsdf3434","SDF3")) // log.Debug("", "get raw data length is", len(list)) log.ZDebug(ctx, "get raw data length is", len(list)) for _, v := range list { temp := sdk_struct.MsgStruct{} temp.ClientMsgID = v.ClientMsgID temp.ServerMsgID = v.ServerMsgID temp.CreateTime = v.CreateTime temp.SendTime = v.SendTime temp.SessionType = v.SessionType temp.SendID = v.SendID temp.RecvID = v.RecvID temp.MsgFrom = v.MsgFrom temp.ContentType = v.ContentType temp.SenderPlatformID = v.SenderPlatformID temp.SenderNickname = v.SenderNickname temp.SenderFaceURL = v.SenderFaceURL temp.Content = v.Content temp.Seq = v.Seq temp.IsRead = v.IsRead temp.Status = v.Status var attachedInfo sdk_struct.AttachedInfoElem _ = utils.JsonStringToStruct(v.AttachedInfo, &attachedInfo) temp.AttachedInfoElem = &attachedInfo temp.Ex = v.Ex temp.LocalEx = v.LocalEx err := c.msgHandleByContentType(&temp) if err != nil { // log.Error("", "Parsing data error:", err.Error(), temp) log.ZError(ctx, "Parsing data error:", err, "msg", temp) continue } if c.filterMsg(&temp, searchParam) { continue } // Determine the conversation ID based on the session type switch temp.SessionType { case constant.SingleChatType: if temp.SendID == c.loginUserID { conversationID = c.getConversationIDBySessionType(temp.RecvID, constant.SingleChatType) } else { conversationID = c.getConversationIDBySessionType(temp.SendID, constant.SingleChatType) } case constant.GroupChatType: temp.GroupID = temp.RecvID temp.RecvID = c.loginUserID conversationID = c.getConversationIDBySessionType(temp.GroupID, constant.GroupChatType) case constant.SuperGroupChatType: temp.GroupID = temp.RecvID temp.RecvID = c.loginUserID conversationID = c.getConversationIDBySessionType(temp.GroupID, constant.SuperGroupChatType) } // Populate the conversationMap with search results if oldItem, ok := conversationMap[conversationID]; !ok { searchResultItem := sdk.SearchByConversationResult{} localConversation, err := c.db.GetConversation(ctx, conversationID) if err != nil { // log.Error("", "get conversation err ", err.Error(), conversationID) continue } searchResultItem.ConversationID = conversationID searchResultItem.FaceURL = localConversation.FaceURL searchResultItem.ShowName = localConversation.ShowName searchResultItem.LatestMsgSendTime = localConversation.LatestMsgSendTime searchResultItem.ConversationType = localConversation.ConversationType searchResultItem.MessageList = append(searchResultItem.MessageList, &temp) searchResultItem.MessageCount++ conversationMap[conversationID] = &searchResultItem } else { oldItem.MessageCount++ oldItem.MessageList = append(oldItem.MessageList, &temp) conversationMap[conversationID] = oldItem } } // Compile the results from the conversationMap into the response structure for _, v := range conversationMap { r.SearchResultItems = append(r.SearchResultItems, v) r.TotalCount += v.MessageCount } // Sort the search results based on the latest message send time sort.Slice(r.SearchResultItems, func(i, j int) bool { return r.SearchResultItems[i].LatestMsgSendTime > r.SearchResultItems[j].LatestMsgSendTime }) return &r, nil // Return the final search results } // true is filter, false is not filter func (c *Conversation) filterMsg(temp *sdk_struct.MsgStruct, searchParam *sdk.SearchLocalMessagesParams) bool { switch temp.ContentType { case constant.Text: return !c.judgeMultipleSubString(searchParam.KeywordList, temp.TextElem.Content, searchParam.KeywordListMatchType) case constant.AtText: return !c.judgeMultipleSubString(searchParam.KeywordList, temp.AtTextElem.Text, searchParam.KeywordListMatchType) case constant.File: return !c.judgeMultipleSubString(searchParam.KeywordList, temp.FileElem.FileName, searchParam.KeywordListMatchType) case constant.Merger: if !c.judgeMultipleSubString(searchParam.KeywordList, temp.MergeElem.Title, searchParam.KeywordListMatchType) { for _, msgStruct := range temp.MergeElem.MultiMessage { if c.filterMsg(msgStruct, searchParam) { continue } else { break } } } case constant.Card: return !c.judgeMultipleSubString(searchParam.KeywordList, temp.CardElem.Nickname, searchParam.KeywordListMatchType) case constant.Location: return !c.judgeMultipleSubString(searchParam.KeywordList, temp.LocationElem.Description, searchParam.KeywordListMatchType) case constant.Custom: return !c.judgeMultipleSubString(searchParam.KeywordList, temp.CustomElem.Description, searchParam.KeywordListMatchType) case constant.Quote: if !c.judgeMultipleSubString(searchParam.KeywordList, temp.QuoteElem.Text, searchParam.KeywordListMatchType) { return c.filterMsg(temp.QuoteElem.QuoteMessage, searchParam) } case constant.Picture: fallthrough case constant.Video: if len(searchParam.KeywordList) == 0 { return false } else { return true } default: return true } return false } func (c *Conversation) delMsgBySeq(seqList []uint32) error { var SPLIT = 1000 for i := 0; i < len(seqList)/SPLIT; i++ { if err := c.delMsgBySeqSplit(seqList[i*SPLIT : (i+1)*SPLIT]); err != nil { return utils.Wrap(err, "") } } return nil } func (c *Conversation) delMsgBySeqSplit(seqList []uint32) error { // var req server_api_params.DelMsgListReq // req.SeqList = seqList // req.OperationID = utils.OperationIDGenerator() // req.OpUserID = c.loginUserID // req.UserID = c.loginUserID // operationID := req.OperationID // err := c.SendReqWaitResp(context.Background(), &req, constant.WsDelMsg, 30, c.loginUserID) // if err != nil { // return utils.Wrap(err, "SendReqWaitResp failed") // } // var delResp server_api_params.DelMsgListResp // err = proto.Unmarshal(resp.Data, &delResp) // if err != nil { // log.Error(operationID, "Unmarshal failed ", err.Error()) // return utils.Wrap(err, "Unmarshal failed") // } return nil } // old WS method //funcation (c *Conversation) deleteMessageFromSvr(callback open_im_sdk_callback.Base, s *sdk_struct.MsgStruct, operationID string) { // seq, err := c.db.GetMsgSeqByClientMsgID(s.ClientMsgID) // common.CheckDBErrCallback(callback, err, operationID) // if seq == 0 { // err = errors.New("seq == 0 ") // common.CheckArgsErrCallback(callback, err, operationID) // } // seqList := []uint32{seq} // err = c.delMsgBySeq(seqList) // common.CheckArgsErrCallback(callback, err, operationID) //} func isContainMessageReaction(reactionType int, list []*sdk_struct.ReactionElem) (bool, *sdk_struct.ReactionElem) { for _, v := range list { if v.Type == reactionType { return true, v } } return false, nil } func isContainUserReactionElem(useID string, list []*sdk_struct.UserReactionElem) (bool, *sdk_struct.UserReactionElem) { for _, v := range list { if v.UserID == useID { return true, v } } return false, nil } func DeleteUserReactionElem(a []*sdk_struct.UserReactionElem, userID string) []*sdk_struct.UserReactionElem { j := 0 for _, v := range a { if v.UserID != userID { a[j] = v j++ } } return a[:j] } func (c *Conversation) setMessageReactionExtensions(ctx context.Context, s *sdk_struct.MsgStruct, req sdk.SetMessageReactionExtensionsParams) ([]*server_api_params.ExtensionResult, error) { return nil, nil //message, err := c.db.GetMessageController(ctx, s) //if err != nil { // return nil, err //} //if message.Status != constant.MsgStatusSendSuccess { // return nil, errors.New("only send success message can modify reaction extensions") //} //if message.SessionType != constant.SuperGroupChatType { // return nil, errors.New("currently only support super group message") // //} //extendMsg, _ := c.db.GetMessageReactionExtension(ctx, message.ClientMsgID) //temp := make(map[string]*server_api_params.KeyValue) //_ = json.Unmarshal(extendMsg.LocalReactionExtensions, &temp) //reqTemp := make(map[string]*server_api_params.KeyValue) //for _, v := range req { // if value, ok := temp[v.TypeKey]; ok { // v.LatestUpdateTime = value.LatestUpdateTime // } // reqTemp[v.TypeKey] = v //} //var sourceID string //switch message.SessionType { //case constant.SingleChatType: // if message.SendID == c.loginUserID { // sourceID = message.RecvID // } else { // sourceID = message.SendID // } //case constant.NotificationChatType: // sourceID = message.RecvID //case constant.GroupChatType, constant.SuperGroupChatType: // sourceID = message.RecvID //} //var apiReq server_api_params.SetMessageReactionExtensionsReq //apiReq.IsReact = message.IsReact //apiReq.ClientMsgID = message.ClientMsgID //apiReq.SourceID = sourceID //apiReq.SessionType = message.SessionType //apiReq.IsExternalExtensions = message.IsExternalExtensions //apiReq.ReactionExtensionList = reqTemp //apiReq.OperationID = "" //apiReq.MsgFirstModifyTime = message.MsgFirstModifyTime //resp, err := util.CallApi[server_api_params.ApiResult](ctx, constant.SetMessageReactionExtensionsRouter, &apiReq) //if err != nil { // return nil, err //} //var msg model_struct.LocalChatLogReactionExtensions //msg.ClientMsgID = message.ClientMsgID //resultKeyMap := make(map[string]*sdkws.KeyValue) //for _, v := range resp.Result { // if v.ErrCode == 0 { // temp := new(sdkws.KeyValue) // temp.TypeKey = v.TypeKey // temp.Value = v.Value // temp.LatestUpdateTime = v.LatestUpdateTime // resultKeyMap[v.TypeKey] = temp // } //} //err = c.db.GetAndUpdateMessageReactionExtension(ctx, message.ClientMsgID, resultKeyMap) //if err != nil { // log.Error("", "GetAndUpdateMessageReactionExtension err:", err.Error()) //} //if !message.IsReact { // message.IsReact = resp.IsReact // message.MsgFirstModifyTime = resp.MsgFirstModifyTime // err = c.db.UpdateMessageController(ctx, message) // if err != nil { // log.Error("", "UpdateMessageController err:", err.Error(), message) // // } //} //return resp.Result, nil } func (c *Conversation) addMessageReactionExtensions(ctx context.Context, s *sdk_struct.MsgStruct, req sdk.AddMessageReactionExtensionsParams) ([]*server_api_params.ExtensionResult, error) { return nil, nil //message, err := c.db.GetMessageController(ctx, s) //if err != nil { // return nil, err //} //if message.Status != constant.MsgStatusSendSuccess || message.Seq == 0 { // return nil, errors.New("only send success message can modify reaction extensions") //} //reqTemp := make(map[string]*server_api_params.KeyValue) //extendMsg, err := c.db.GetMessageReactionExtension(ctx, message.ClientMsgID) //if err == nil && extendMsg != nil { // temp := make(map[string]*server_api_params.KeyValue) // _ = json.Unmarshal(extendMsg.LocalReactionExtensions, &temp) // for _, v := range req { // if value, ok := temp[v.TypeKey]; ok { // v.LatestUpdateTime = value.LatestUpdateTime // } // reqTemp[v.TypeKey] = v // } //} else { // for _, v := range req { // reqTemp[v.TypeKey] = v // } //} //var sourceID string //switch message.SessionType { //case constant.SingleChatType: // if message.SendID == c.loginUserID { // sourceID = message.RecvID // } else { // sourceID = message.SendID // } //case constant.NotificationChatType: // sourceID = message.RecvID //case constant.GroupChatType, constant.SuperGroupChatType: // sourceID = message.RecvID //} //var apiReq server_api_params.AddMessageReactionExtensionsReq //apiReq.IsReact = message.IsReact //apiReq.ClientMsgID = message.ClientMsgID //apiReq.SourceID = sourceID //apiReq.SessionType = message.SessionType //apiReq.IsExternalExtensions = message.IsExternalExtensions //apiReq.ReactionExtensionList = reqTemp //apiReq.OperationID = "" //apiReq.MsgFirstModifyTime = message.MsgFirstModifyTime //apiReq.Seq = message.Seq // //resp, err := util.CallApi[server_api_params.ApiResult](ctx, constant.AddMessageReactionExtensionsRouter, &apiReq) //if err != nil { // return nil, err //} //log.Debug("", "api return:", message.IsReact, resp) //if !message.IsReact { // message.IsReact = resp.IsReact // message.MsgFirstModifyTime = resp.MsgFirstModifyTime // err = c.db.UpdateMessageController(ctx, message) // if err != nil { // log.Error("", "UpdateMessageController err:", err.Error(), message) // } //} //return resp.Result, nil } func (c *Conversation) deleteMessageReactionExtensions(ctx context.Context, s *sdk_struct.MsgStruct, req sdk.DeleteMessageReactionExtensionsParams) ([]*server_api_params.ExtensionResult, error) { // message, err := c.GetMessageController(ctx, s) // if err != nil { // return nil, err // } // if message.Status != constant.MsgStatusSendSuccess { // return nil, errors.New("only send success message can modify reaction extensions") // } // if message.SessionType != constant.SuperGroupChatType { // return nil, errors.New("currently only support super group message") // } // extendMsg, _ := c.db.GetMessageReactionExtension(ctx, message.ClientMsgID) // temp := make(map[string]*server_api_params.KeyValue) // _ = json.Unmarshal(extendMsg.LocalReactionExtensions, &temp) // var reqTemp []*server_api_params.KeyValue // for _, v := range req { // if value, ok := temp[v]; ok { // var tt server_api_params.KeyValue // tt.LatestUpdateTime = value.LatestUpdateTime // tt.TypeKey = v // reqTemp = append(reqTemp, &tt) // } // } // var sourceID string // switch message.SessionType { // case constant.SingleChatType: // if message.SendID == c.loginUserID { // sourceID = message.RecvID // } else { // sourceID = message.SendID // } // case constant.NotificationChatType: // sourceID = message.RecvID // case constant.GroupChatType, constant.SuperGroupChatType: // sourceID = message.RecvID // } // var apiReq server_api_params.DeleteMessageReactionExtensionsReq // apiReq.ClientMsgID = message.ClientMsgID // apiReq.SourceID = sourceID // apiReq.SessionType = message.SessionType // apiReq.ReactionExtensionList = reqTemp // apiReq.OperationID = "" // apiReq.IsExternalExtensions = message.IsExternalExtensions // apiReq.MsgFirstModifyTime = message.MsgFirstModifyTime // resp, err := util.CallApi[server_api_params.ApiResult](ctx, constant.AddMessageReactionExtensionsRouter, &apiReq) // if err != nil { // return nil, err // } // var msg model_struct.LocalChatLogReactionExtensions // msg.ClientMsgID = message.ClientMsgID // resultKeyMap := make(map[string]*sdkws.KeyValue) // for _, v := range resp.Result { // if v.ErrCode == 0 { // temp := new(sdkws.KeyValue) // temp.TypeKey = v.TypeKey // resultKeyMap[v.TypeKey] = temp // } // } // err = c.db.DeleteAndUpdateMessageReactionExtension(ctx, message.ClientMsgID, resultKeyMap) // if err != nil { // log.Error("", "GetAndUpdateMessageReactionExtension err:", err.Error()) // } // return resp.Result, nil return nil, nil } type syncReactionExtensionParams struct { MessageList []*model_struct.LocalChatLog SessionType int32 SourceID string IsExternalExtension bool ExtendMessageList []*model_struct.LocalChatLogReactionExtensions TypeKeyList []string } func (c *Conversation) getMessageListReactionExtensions(ctx context.Context, conversationID string, messageList []*sdk_struct.MsgStruct) ([]*server_api_params.SingleMessageExtensionResult, error) { if len(messageList) == 0 { return nil, errors.New("message list is null") } var msgIDs []string var sourceID string var sessionType int32 var isExternalExtension bool for _, msgStruct := range messageList { switch msgStruct.SessionType { case constant.SingleChatType: if msgStruct.SendID == c.loginUserID { sourceID = msgStruct.RecvID } else { sourceID = msgStruct.SendID } case constant.NotificationChatType: sourceID = msgStruct.RecvID case constant.GroupChatType, constant.SuperGroupChatType: sourceID = msgStruct.GroupID } sessionType = msgStruct.SessionType msgIDs = append(msgIDs, msgStruct.ClientMsgID) } isExternalExtension = c.IsExternalExtensions localMessageList, err := c.db.GetMessagesByClientMsgIDs(ctx, conversationID, msgIDs) if err != nil { return nil, err } for _, v := range localMessageList { if v.IsReact != true { return nil, errors.New("have not reaction message in message list:" + v.ClientMsgID) } } var result server_api_params.GetMessageListReactionExtensionsResp extendMessage, _ := c.db.GetMultipleMessageReactionExtension(ctx, msgIDs) for _, v := range extendMessage { var singleResult server_api_params.SingleMessageExtensionResult // temp := make(map[string]*sdkws.KeyValue) // _ = json.Unmarshal(v.LocalReactionExtensions, &temp) singleResult.ClientMsgID = v.ClientMsgID // singleResult.ReactionExtensionList = temp result = append(result, &singleResult) } args := syncReactionExtensionParams{} args.MessageList = localMessageList args.SourceID = sourceID args.SessionType = sessionType args.ExtendMessageList = extendMessage args.IsExternalExtension = isExternalExtension _ = common.TriggerCmdSyncReactionExtensions(common.SyncReactionExtensionsNode{ OperationID: "", Action: constant.SyncMessageListReactionExtensions, Args: args, }, c.GetCh()) return result, nil } // funcation (c *Conversation) getMessageListSomeReactionExtensions(callback open_im_sdk_callback.Base, messageList []*sdk_struct.MsgStruct, keyList []string, operationID string) server_api_params.GetMessageListReactionExtensionsResp { // if len(messageList) == 0 { // common.CheckAnyErrCallback(callback, 201, errors.New("message list is null"), operationID) // } // var msgIDList []string // var sourceID string // var sessionType int32 // var isExternalExtension bool // for _, msgStruct := range messageList { // switch msgStruct.SessionType { // case constant.SingleChatType: // if msgStruct.SendID == c.loginUserID { // sourceID = msgStruct.RecvID // } else { // sourceID = msgStruct.SendID // } // case constant.NotificationChatType: // sourceID = msgStruct.RecvID // case constant.GroupChatType, constant.SuperGroupChatType: // sourceID = msgStruct.GroupID // } // sessionType = msgStruct.SessionType // isExternalExtension = msgStruct.IsExternalExtensions // msgIDList = append(msgIDList, msgStruct.ClientMsgID) // } // localMessageList, err := c.db.GetMultipleMessageController(msgIDList, sourceID, sessionType) // common.CheckDBErrCallback(callback, err, operationID) // var result server_api_params.GetMessageListReactionExtensionsResp // extendMsgs, _ := c.db.GetMultipleMessageReactionExtension(msgIDList) // for _, v := range extendMsgs { // var singleResult server_api_params.SingleMessageExtensionResult // temp := make(map[string]*server_api_params.KeyValue) // _ = json.Unmarshal(v.LocalReactionExtensions, &temp) // for s, _ := range temp { // if !utils.IsContain(s, keyList) { // delete(temp, s) // } // } // singleResult.ClientMsgID = v.ClientMsgID // singleResult.ReactionExtensionList = temp // result = append(result, &singleResult) // } // args := syncReactionExtensionParams{} // args.MessageList = localMessageList // args.SourceID = sourceID // args.TypeKeyList = keyList // args.SessionType = sessionType // args.ExtendMessageList = extendMsgs // args.IsExternalExtension = isExternalExtension // _ = common.TriggerCmdSyncReactionExtensions(common.SyncReactionExtensionsNode{ // OperationID: operationID, // Action: constant.SyncMessageListReactionExtensions, // Args: args, // }, c.GetCh()) // return result // } // // funcation (c *Conversation) setTypeKeyInfo(callback open_im_sdk_callback.Base, s *sdk_struct.MsgStruct, typeKey, ex string, isCanRepeat bool, operationID string) []*server_api_params.ExtensionResult { // message, err := c.db.GetMessageController(s) // common.CheckDBErrCallback(callback, err, operationID) // if message.Status != constant.MsgStatusSendSuccess { // common.CheckAnyErrCallback(callback, 201, errors.New("only send success message can modify reaction extensions"), operationID) // } // extendMsg, _ := c.db.GetMessageReactionExtension(message.ClientMsgID) // temp := make(map[string]*server_api_params.KeyValue) // _ = json.Unmarshal(extendMsg.LocalReactionExtensions, &temp) // var flag bool // var isContainSelfK string // var dbIsCanRepeat bool // var deletedKeyValue server_api_params.KeyValue // var maxTypeKey string // var maxTypeKeyValue server_api_params.KeyValue // reqTemp := make(map[string]*server_api_params.KeyValue) // for k, v := range temp { // if strings.HasPrefix(k, typeKey) { // flag = true // singleTypeKeyInfo := new(sdk.SingleTypeKeyInfo) // _ = json.Unmarshal([]byte(v.Value), singleTypeKeyInfo) // if _, ok := singleTypeKeyInfo.InfoList[c.loginUserID]; ok { // isContainSelfK = k // dbIsCanRepeat = singleTypeKeyInfo.IsCanRepeat // delete(singleTypeKeyInfo.InfoList, c.loginUserID) // singleTypeKeyInfo.Counter-- // deletedKeyValue.TypeKey = v.TypeKey // deletedKeyValue.Value = utils.StructToJsonString(singleTypeKeyInfo) // deletedKeyValue.LatestUpdateTime = v.LatestUpdateTime // } // if k > maxTypeKey { // maxTypeKey = k // maxTypeKeyValue = *v // } // } // } // if !flag { // if len(temp) >= 300 { // common.CheckAnyErrCallback(callback, 202, errors.New("number of keys only can support 300"), operationID) // } // singleTypeKeyInfo := new(sdk.SingleTypeKeyInfo) // singleTypeKeyInfo.TypeKey = getIndexTypeKey(typeKey, 0) // singleTypeKeyInfo.Counter = 1 // singleTypeKeyInfo.IsCanRepeat = isCanRepeat // singleTypeKeyInfo.Index = 0 // userInfo := new(sdk.Info) // userInfo.UserID = c.loginUserID // userInfo.Ex = ex // singleTypeKeyInfo.InfoList[c.loginUserID] = userInfo // keyValue := new(server_api_params.KeyValue) // keyValue.TypeKey = singleTypeKeyInfo.TypeKey // keyValue.Value = utils.StructToJsonString(singleTypeKeyInfo) // reqTemp[singleTypeKeyInfo.TypeKey] = keyValue // } else { // if isContainSelfK != "" && !dbIsCanRepeat { // //删除操作 // reqTemp[isContainSelfK] = &deletedKeyValue // } else { // singleTypeKeyInfo := new(sdk.SingleTypeKeyInfo) // _ = json.Unmarshal([]byte(maxTypeKeyValue.Value), singleTypeKeyInfo) // userInfo := new(sdk.Info) // userInfo.UserID = c.loginUserID // userInfo.Ex = ex // singleTypeKeyInfo.Counter++ // singleTypeKeyInfo.InfoList[c.loginUserID] = userInfo // maxTypeKeyValue.Value = utils.StructToJsonString(singleTypeKeyInfo) // data, _ := json.Marshal(maxTypeKeyValue) // if len(data) > 1000 { //单key超过了1kb // if len(temp) >= 300 { // common.CheckAnyErrCallback(callback, 202, errors.New("number of keys only can support 300"), operationID) // } // newSingleTypeKeyInfo := new(sdk.SingleTypeKeyInfo) // newSingleTypeKeyInfo.TypeKey = getIndexTypeKey(typeKey, singleTypeKeyInfo.Index+1) // newSingleTypeKeyInfo.Counter = 1 // newSingleTypeKeyInfo.IsCanRepeat = singleTypeKeyInfo.IsCanRepeat // newSingleTypeKeyInfo.Index = singleTypeKeyInfo.Index + 1 // userInfo := new(sdk.Info) // userInfo.UserID = c.loginUserID // userInfo.Ex = ex // newSingleTypeKeyInfo.InfoList[c.loginUserID] = userInfo // keyValue := new(server_api_params.KeyValue) // keyValue.TypeKey = newSingleTypeKeyInfo.TypeKey // keyValue.Value = utils.StructToJsonString(newSingleTypeKeyInfo) // reqTemp[singleTypeKeyInfo.TypeKey] = keyValue // } else { // reqTemp[maxTypeKey] = &maxTypeKeyValue // } // // } // } // var sourceID string // switch message.SessionType { // case constant.SingleChatType: // sourceID = message.SendID + message.RecvID // case constant.NotificationChatType: // sourceID = message.RecvID // case constant.GroupChatType, constant.SuperGroupChatType: // sourceID = message.RecvID // } // var apiReq server_api_params.SetMessageReactionExtensionsReq // apiReq.IsReact = message.IsReact // apiReq.ClientMsgID = message.ClientMsgID // apiReq.SourceID = sourceID // apiReq.SessionType = message.SessionType // apiReq.IsExternalExtensions = message.IsExternalExtensions // apiReq.ReactionExtensionList = reqTemp // apiReq.OperationID = operationID // apiReq.MsgFirstModifyTime = message.MsgFirstModifyTime // var apiResp server_api_params.SetMessageReactionExtensionsResp // c.p.PostFatalCallback(callback, constant.SetMessageReactionExtensionsRouter, apiReq, &apiResp.ApiResult, apiReq.OperationID) // var msg model_struct.LocalChatLogReactionExtensions // msg.ClientMsgID = message.ClientMsgID // resultKeyMap := make(map[string]*server_api_params.KeyValue) // for _, v := range apiResp.ApiResult.Result { // if v.ErrCode == 0 { // temp := new(server_api_params.KeyValue) // temp.TypeKey = v.TypeKey // temp.Value = v.Value // temp.LatestUpdateTime = v.LatestUpdateTime // resultKeyMap[v.TypeKey] = temp // } // } // err = c.db.GetAndUpdateMessageReactionExtension(message.ClientMsgID, resultKeyMap) // if err != nil { // log.Error(operationID, "GetAndUpdateMessageReactionExtension err:", err.Error()) // } // if !message.IsReact { // message.IsReact = apiResp.ApiResult.IsReact // message.MsgFirstModifyTime = apiResp.ApiResult.MsgFirstModifyTime // err = c.db.UpdateMessageController(message) // if err != nil { // log.Error(operationID, "UpdateMessageController err:", err.Error(), message) // // } // } // return apiResp.ApiResult.Result // } // // funcation getIndexTypeKey(typeKey string, index int) string { // return typeKey + "$" + utils.IntToString(index) // } func getPrefixTypeKey(typeKey string) string { list := strings.Split(typeKey, "$") if len(list) > 0 { return list[0] } return "" } //funcation (c *Conversation) getTypeKeyListInfo(callback open_im_sdk_callback.Base, s *sdk_struct.MsgStruct, keyList []string, operationID string) (result []*sdk.SingleTypeKeyInfoSum) { // message, err := c.db.GetMessageController(s) // common.CheckDBErrCallback(callback, err, operationID) // if message.Status != constant.MsgStatusSendSuccess { // common.CheckAnyErrCallback(callback, 201, errors.New("only send success message can modify reaction extensions"), operationID) // } // if !message.IsReact { // common.CheckAnyErrCallback(callback, 202, errors.New("can get message reaction ex"), operationID) // } // extendMsg, _ := c.db.GetMessageReactionExtension(message.ClientMsgID) // temp := make(map[string]*server_api_params.KeyValue) // _ = json.Unmarshal(extendMsg.LocalReactionExtensions, &temp) // for _, v := range keyList { // singleResult := new(sdk.SingleTypeKeyInfoSum) // singleResult.TypeKey = v // for typeKey, value := range temp { // if strings.HasPrefix(typeKey, v) { // singleTypeKeyInfo := new(sdk.SingleTypeKeyInfo) // _ = json.Unmarshal([]byte(value.Value), singleTypeKeyInfo) // if _, ok := singleTypeKeyInfo.InfoList[c.loginUserID]; ok { // singleResult.IsContainSelf = true // } // for _, info := range singleTypeKeyInfo.InfoList { // v := *info // singleResult.InfoList = append(singleResult.InfoList, &v) // } // singleResult.Counter += singleTypeKeyInfo.Counter // } // } // result = append(result, singleResult) // } // messageList := []*sdk_struct.MsgStruct{s} // _ = common.TriggerCmdSyncReactionExtensions(common.SyncReactionExtensionsNode{ // OperationID: operationID, // Action: constant.SyncMessageListTypeKeyInfo, // Args: messageList, // }, c.GetCh()) // // return result //} // //funcation (c *Conversation) getAllTypeKeyInfo(callback open_im_sdk_callback.Base, s *sdk_struct.MsgStruct, operationID string) (result []*sdk.SingleTypeKeyInfoSum) { // message, err := c.db.GetMessageController(s) // common.CheckDBErrCallback(callback, err, operationID) // if message.Status != constant.MsgStatusSendSuccess { // common.CheckAnyErrCallback(callback, 201, errors.New("only send success message can modify reaction extensions"), operationID) // } // if !message.IsReact { // common.CheckAnyErrCallback(callback, 202, errors.New("can get message reaction ex"), operationID) // } // extendMsg, _ := c.db.GetMessageReactionExtension(message.ClientMsgID) // temp := make(map[string]*server_api_params.KeyValue) // _ = json.Unmarshal(extendMsg.LocalReactionExtensions, &temp) // mapResult := make(map[string]*sdk.SingleTypeKeyInfoSum) // for typeKey, value := range temp { // singleTypeKeyInfo := new(sdk.SingleTypeKeyInfo) // err := json.Unmarshal([]byte(value.Value), singleTypeKeyInfo) // if err != nil { // log.Warn(operationID, "not this type ", value.Value) // continue // } // prefixKey := getPrefixTypeKey(typeKey) // if v, ok := mapResult[prefixKey]; ok { // for _, info := range singleTypeKeyInfo.InfoList { // t := *info // v.InfoList = append(v.InfoList, &t) // } // if _, ok := singleTypeKeyInfo.InfoList[c.loginUserID]; ok { // v.IsContainSelf = true // } // v.Counter += singleTypeKeyInfo.Counter // } else { // v := new(sdk.SingleTypeKeyInfoSum) // v.TypeKey = prefixKey // v.Counter = singleTypeKeyInfo.Counter // for _, info := range singleTypeKeyInfo.InfoList { // t := *info // v.InfoList = append(v.InfoList, &t) // } // if _, ok := singleTypeKeyInfo.InfoList[c.loginUserID]; ok { // v.IsContainSelf = true // } // mapResult[prefixKey] = v // } // } // for _, v := range mapResult { // result = append(result, v) // // } // return result //}