Improve message forward compatibility
This commit is contained in:
+184
-19
@@ -425,13 +425,37 @@ type imFavoritesForwardRequest struct {
|
||||
type imMessagesForwardRequest struct {
|
||||
Mode string `json:"mode"`
|
||||
Sources []struct {
|
||||
Topic string `json:"topic"`
|
||||
Seq int64 `json:"seq,string"`
|
||||
Topic string `json:"topic"`
|
||||
Seq imForwardSeq `json:"seq"`
|
||||
} `json:"sources"`
|
||||
TargetTopics []string `json:"target_topics"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type imForwardSeq int64
|
||||
|
||||
func (s *imForwardSeq) UnmarshalJSON(data []byte) error {
|
||||
var number int64
|
||||
if err := json.Unmarshal(data, &number); err == nil {
|
||||
*s = imForwardSeq(number)
|
||||
return nil
|
||||
}
|
||||
var text string
|
||||
if err := json.Unmarshal(data, &text); err != nil {
|
||||
return err
|
||||
}
|
||||
text = strings.TrimSpace(text)
|
||||
if text == "" {
|
||||
return nil
|
||||
}
|
||||
parsed, err := strconv.ParseInt(text, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = imForwardSeq(parsed)
|
||||
return nil
|
||||
}
|
||||
|
||||
type imFriendRequestCreateRequest struct {
|
||||
TargetUserID string `json:"target_user_id"`
|
||||
Message string `json:"message,omitempty"`
|
||||
@@ -3529,8 +3553,8 @@ func serveIMMessageForward(wrt http.ResponseWriter, req *http.Request) {
|
||||
if mode == "" {
|
||||
mode = "separate"
|
||||
}
|
||||
if mode != "separate" {
|
||||
writeIMError(wrt, req, http.StatusNotImplemented, 30004, "NOT_IMPLEMENTED", "forward bundle is not implemented", map[string]any{"field": "mode"})
|
||||
if mode != "separate" && mode != "bundle" {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "mode"})
|
||||
return
|
||||
}
|
||||
if len(body.Sources) == 0 || len(body.Sources) > 100 || len(body.TargetTopics) == 0 || len(body.TargetTopics) > 20 {
|
||||
@@ -3542,10 +3566,16 @@ func serveIMMessageForward(wrt http.ResponseWriter, req *http.Request) {
|
||||
items := make([]map[string]any, 0, len(body.TargetTopics)*len(body.Sources))
|
||||
failed := make([]map[string]any, 0)
|
||||
for _, rawTarget := range body.TargetTopics {
|
||||
target := strings.TrimSpace(rawTarget)
|
||||
target, targetOK := normalizeIMForwardTopic(principal.Uid, rawTarget)
|
||||
if target == "" {
|
||||
for _, source := range body.Sources {
|
||||
failed = append(failed, forwardFailurePayload(source.Topic, source.Seq, target, imReasonMalformedRequest))
|
||||
failed = append(failed, forwardFailurePayload(source.Topic, int64(source.Seq), strings.TrimSpace(rawTarget), imReasonMalformedRequest))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !targetOK {
|
||||
for _, source := range body.Sources {
|
||||
failed = append(failed, forwardFailurePayload(source.Topic, int64(source.Seq), target, imReasonMalformedRequest))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -3556,14 +3586,44 @@ func serveIMMessageForward(wrt http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
if targetErr != nil {
|
||||
for _, source := range body.Sources {
|
||||
failed = append(failed, forwardFailurePayload(source.Topic, source.Seq, target, relationErrorReason(targetErr)))
|
||||
failed = append(failed, forwardFailurePayload(source.Topic, int64(source.Seq), target, relationErrorReason(targetErr)))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if mode == "bundle" {
|
||||
bundle, sourceFailures := buildForwardBundle(principal, body.Sources, target, sourceReadable, body.Comment)
|
||||
if len(sourceFailures) > 0 {
|
||||
failed = append(failed, sourceFailures...)
|
||||
continue
|
||||
}
|
||||
clientMid := "fwd_" + store.Store.GetUidString()
|
||||
forwarded := &types.Message{
|
||||
TenantID: principal.TenantID,
|
||||
Topic: target,
|
||||
From: principal.Uid.String(),
|
||||
Head: forwardBundleMessageHead(clientMid),
|
||||
Content: bundle,
|
||||
}
|
||||
saved, err := saveMessageMaybeKafka(req.Context(), principal.TenantID, forwarded, nil, true)
|
||||
if err != nil {
|
||||
failed = append(failed, forwardFailurePayload("", 0, target, relationErrorReason(err)))
|
||||
continue
|
||||
}
|
||||
items = append(items, map[string]any{
|
||||
"sources": bundle["source_refs"],
|
||||
"target_topic": target,
|
||||
"client_mid": clientMid,
|
||||
"seq": saved.SeqId,
|
||||
"status": "sent",
|
||||
"mode": "bundle",
|
||||
"bundle_id": bundle["bundle_id"],
|
||||
})
|
||||
continue
|
||||
}
|
||||
for _, source := range body.Sources {
|
||||
sourceTopic := strings.TrimSpace(source.Topic)
|
||||
if sourceTopic == "" || source.Seq <= 0 {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, imReasonMalformedRequest))
|
||||
sourceTopic, sourceOK := normalizeIMForwardTopic(principal.Uid, source.Topic)
|
||||
if sourceTopic == "" || int64(source.Seq) <= 0 || !sourceOK {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, int64(source.Seq), target, imReasonMalformedRequest))
|
||||
continue
|
||||
}
|
||||
readErr, ok := sourceReadable[sourceTopic]
|
||||
@@ -3572,20 +3632,20 @@ func serveIMMessageForward(wrt http.ResponseWriter, req *http.Request) {
|
||||
sourceReadable[sourceTopic] = readErr
|
||||
}
|
||||
if readErr != nil {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, relationErrorReason(readErr)))
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, int64(source.Seq), target, relationErrorReason(readErr)))
|
||||
continue
|
||||
}
|
||||
msg, err := store.Messages.GetOne(principal.TenantID, sourceTopic, source.Seq)
|
||||
msg, err := store.Messages.GetOne(principal.TenantID, sourceTopic, int64(source.Seq))
|
||||
if err != nil {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, relationErrorReason(err)))
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, int64(source.Seq), target, relationErrorReason(err)))
|
||||
continue
|
||||
}
|
||||
if msg == nil {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, "NOT_FOUND"))
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, int64(source.Seq), target, "NOT_FOUND"))
|
||||
continue
|
||||
}
|
||||
if messageAvailability(*msg) != "available" {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, "MESSAGE_UNAVAILABLE"))
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, int64(source.Seq), target, "MESSAGE_UNAVAILABLE"))
|
||||
continue
|
||||
}
|
||||
clientMid := "fwd_" + store.Store.GetUidString()
|
||||
@@ -3598,11 +3658,11 @@ func serveIMMessageForward(wrt http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
saved, err := saveMessageMaybeKafka(req.Context(), principal.TenantID, forwarded, nil, true)
|
||||
if err != nil {
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, source.Seq, target, relationErrorReason(err)))
|
||||
failed = append(failed, forwardFailurePayload(sourceTopic, int64(source.Seq), target, relationErrorReason(err)))
|
||||
continue
|
||||
}
|
||||
items = append(items, map[string]any{
|
||||
"source": map[string]any{"topic": sourceTopic, "seq": source.Seq},
|
||||
"source": map[string]any{"topic": sourceTopic, "seq": int64(source.Seq)},
|
||||
"target_topic": target,
|
||||
"client_mid": clientMid,
|
||||
"seq": saved.SeqId,
|
||||
@@ -5455,8 +5515,16 @@ func serveIMGroupMemberProfile(wrt http.ResponseWriter, req *http.Request, topic
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if types.GetTopicCat(topic) != types.TopicCatGrp || memberID.IsZero() {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", nil)
|
||||
if types.GetTopicCat(topic) != types.TopicCatGrp {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{
|
||||
"field": "topic",
|
||||
"expected": "group topic, e.g. grp...",
|
||||
"actual": topic,
|
||||
})
|
||||
return
|
||||
}
|
||||
if memberID.IsZero() {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "user_id"})
|
||||
return
|
||||
}
|
||||
selfSub, err := store.Subs.Get(principal.TenantID, topic, principal.Uid, false)
|
||||
@@ -8791,6 +8859,94 @@ func forwardFailurePayload(topic string, seq int64, target, reason string) map[s
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeIMForwardTopic(viewer types.Uid, topic string) (string, bool) {
|
||||
topic = strings.TrimSpace(topic)
|
||||
if topic == "" {
|
||||
return "", false
|
||||
}
|
||||
if len(topic) < 3 {
|
||||
return "", false
|
||||
}
|
||||
switch types.GetTopicCat(topic) {
|
||||
case types.TopicCatMe:
|
||||
peer := types.ParseUserId(topic)
|
||||
if peer.IsZero() {
|
||||
return "", false
|
||||
}
|
||||
p2p := viewer.P2PName(peer)
|
||||
return p2p, p2p != ""
|
||||
case types.TopicCatP2P, types.TopicCatGrp, types.TopicCatSlf:
|
||||
return topic, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func buildForwardBundle(principal *imPrincipal, sources []struct {
|
||||
Topic string `json:"topic"`
|
||||
Seq imForwardSeq `json:"seq"`
|
||||
}, target string, sourceReadable map[string]error, comment string) (map[string]any, []map[string]any) {
|
||||
items := make([]map[string]any, 0, len(sources))
|
||||
preview := make([]map[string]any, 0, min(len(sources), 3))
|
||||
sourceRefs := make([]map[string]any, 0, len(sources))
|
||||
failures := make([]map[string]any, 0)
|
||||
for _, source := range sources {
|
||||
sourceTopic, sourceOK := normalizeIMForwardTopic(principal.Uid, source.Topic)
|
||||
if sourceTopic == "" || int64(source.Seq) <= 0 || !sourceOK {
|
||||
failures = append(failures, forwardFailurePayload(sourceTopic, int64(source.Seq), target, imReasonMalformedRequest))
|
||||
continue
|
||||
}
|
||||
readErr, ok := sourceReadable[sourceTopic]
|
||||
if !ok {
|
||||
readErr = requireTopicReader(principal, sourceTopic)
|
||||
sourceReadable[sourceTopic] = readErr
|
||||
}
|
||||
if readErr != nil {
|
||||
failures = append(failures, forwardFailurePayload(sourceTopic, int64(source.Seq), target, relationErrorReason(readErr)))
|
||||
continue
|
||||
}
|
||||
msg, err := store.Messages.GetOne(principal.TenantID, sourceTopic, int64(source.Seq))
|
||||
if err != nil {
|
||||
failures = append(failures, forwardFailurePayload(sourceTopic, int64(source.Seq), target, relationErrorReason(err)))
|
||||
continue
|
||||
}
|
||||
if msg == nil {
|
||||
failures = append(failures, forwardFailurePayload(sourceTopic, int64(source.Seq), target, "NOT_FOUND"))
|
||||
continue
|
||||
}
|
||||
if messageAvailability(*msg) != "available" {
|
||||
failures = append(failures, forwardFailurePayload(sourceTopic, int64(source.Seq), target, "MESSAGE_UNAVAILABLE"))
|
||||
continue
|
||||
}
|
||||
snapshot := favoriteSnapshot(msg)
|
||||
items = append(items, snapshot)
|
||||
ref := map[string]any{"topic": sourceTopic, "seq": int64(source.Seq)}
|
||||
sourceRefs = append(sourceRefs, ref)
|
||||
if len(preview) < 3 {
|
||||
preview = append(preview, map[string]any{
|
||||
"from": msg.From,
|
||||
"type": imMessageType(msg),
|
||||
"summary": textSnippet(messageText(*msg), ""),
|
||||
"ts": msg.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return nil, failures
|
||||
}
|
||||
bundleID := "fbundle_" + store.Store.GetUidString()
|
||||
return map[string]any{
|
||||
"bundle_id": bundleID,
|
||||
"title": "聊天记录",
|
||||
"item_count": len(items),
|
||||
"preview": preview,
|
||||
"items": items,
|
||||
"source_refs": sourceRefs,
|
||||
"comment": strings.TrimSpace(comment),
|
||||
"forwarded": true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func forwardedMessageHead(msg *types.Message, clientMid string) types.KVMap {
|
||||
head := types.KVMap{}
|
||||
if msg != nil {
|
||||
@@ -8811,6 +8967,15 @@ func forwardedMessageHead(msg *types.Message, clientMid string) types.KVMap {
|
||||
return head
|
||||
}
|
||||
|
||||
func forwardBundleMessageHead(clientMid string) types.KVMap {
|
||||
return types.KVMap{
|
||||
"type": "forward_bundle",
|
||||
"x-im-type": "forward_bundle",
|
||||
"x-im-forwarded": true,
|
||||
"x-im-client-mid": clientMid,
|
||||
}
|
||||
}
|
||||
|
||||
func forwardedMessageContent(msg *types.Message, comment string) any {
|
||||
if strings.TrimSpace(comment) == "" {
|
||||
if msg == nil {
|
||||
|
||||
Reference in New Issue
Block a user