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 := normalizedSearchText(query.Query) groups := make([]Group, 0, len(unique)) for _, group := range unique { if groupMatchesQuery(group, queryText) { groups = append(groups, group) } } sort.Slice(groups, func(i, j int) bool { leftRank := groupSearchRank(groups[i], queryText) rightRank := groupSearchRank(groups[j], queryText) if leftRank != rightRank { return leftRank < rightRank } leftID := strings.ToLower(groups[i].GroupID) rightID := strings.ToLower(groups[j].GroupID) if leftID != rightID { return leftID < rightID } return groups[i].GroupID < groups[j].GroupID }) limit := query.Limit if limit <= 0 || limit > len(groups) { limit = len(groups) } return SearchGroupsResult{Groups: groups[:limit]} } func addMessageGroup(unique map[string]Group, id string) { trimmed := strings.TrimSpace(id) if trimmed == "" || !isLikelyGroupJID(trimmed) { return } key := strings.ToLower(trimmed) if _, exists := unique[key]; exists { return } unique[key] = 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") } func groupMatchesQuery(group Group, queryText string) bool { if queryText == "" { return true } return strings.Contains(strings.ToLower(group.GroupID), queryText) || strings.Contains(strings.ToLower(group.DisplayName), queryText) } func groupSearchRank(group Group, queryText string) int { if queryText == "" { return 0 } values := []string{ strings.ToLower(group.GroupID), strings.ToLower(group.DisplayName), } for _, value := range values { if value == queryText { return 0 } } for _, value := range values { if strings.HasPrefix(value, queryText) { return 1 } } return 2 }