package isphere import ( "sort" "strings" ) type Group struct { GroupID string DisplayName string Source string Confidence float64 RawRef string } type SearchGroupsQuery struct { Query string Limit int } type SearchGroupsResult struct { Groups []Group } func SearchGroupsFromMessages(messages []Message, query SearchGroupsQuery) SearchGroupsResult { unique := map[string]Group{} for _, message := range messages { if strings.EqualFold(message.ConversationType, "groupchat") { addMessageGroup(unique, message.SenderID) addMessageGroup(unique, message.ReceiverID) continue } if isLikelyGroupJID(message.SenderID) { addMessageGroup(unique, message.SenderID) } if isLikelyGroupJID(message.ReceiverID) { addMessageGroup(unique, message.ReceiverID) } } queryText := strings.ToLower(strings.TrimSpace(query.Query)) ids := make([]string, 0, len(unique)) for id := range unique { if queryText == "" || strings.Contains(strings.ToLower(id), queryText) || strings.Contains(strings.ToLower(unique[id].DisplayName), queryText) { ids = append(ids, id) } } sort.Strings(ids) limit := query.Limit if limit <= 0 || limit > len(ids) { limit = len(ids) } groups := make([]Group, 0, limit) for _, id := range ids[:limit] { groups = append(groups, unique[id]) } return SearchGroupsResult{Groups: groups} } func addMessageGroup(unique map[string]Group, id string) { trimmed := strings.TrimSpace(id) if trimmed == "" || !isLikelyGroupJID(trimmed) { return } if _, exists := unique[trimmed]; exists { return } unique[trimmed] = Group{ GroupID: trimmed, DisplayName: trimmed, Source: "local_readonly", Confidence: 0.6, RawRef: "groupchat_jid", } } func isLikelyGroupJID(id string) bool { lower := strings.ToLower(strings.TrimSpace(id)) return strings.Contains(lower, "conference") || strings.Contains(lower, "muc") || strings.Contains(lower, "group") }