thread-master/backend/internal/library/viral/merge_accounts.go

191 lines
4.9 KiB
Go
Raw Normal View History

2026-06-28 08:28:42 +00:00
package viral
import (
"sort"
"strings"
missionentity "haixun-backend/internal/model/copy_mission/domain/entity"
)
// MergeSimilarAccounts merges previous and newly discovered similar accounts
// keyed by lowercase username. Newly discovered accounts overwrite per-account
// statistics, but pinned/excluded status and previously seen accounts are
// preserved. Pinned accounts stay at the top even when not refreshed.
func MergeSimilarAccounts(prev, next []missionentity.SimilarAccount) []missionentity.SimilarAccount {
if len(prev) == 0 {
return capSimilarAccounts(append([]missionentity.SimilarAccount(nil), next...))
}
if len(next) == 0 {
return prioritizePinnedSimilarAccounts(append([]missionentity.SimilarAccount(nil), prev...))
}
byUser := map[string]missionentity.SimilarAccount{}
newOrder := make([]string, 0, len(next))
for _, item := range next {
key := strings.ToLower(strings.TrimSpace(item.Username))
if key == "" {
continue
}
if _, ok := byUser[key]; !ok {
newOrder = append(newOrder, key)
}
byUser[key] = mergeAccountRecord(byUser[key], item)
}
prevOrder := make([]string, 0, len(prev))
for _, item := range prev {
key := strings.ToLower(strings.TrimSpace(item.Username))
if key == "" {
continue
}
if _, ok := byUser[key]; ok {
byUser[key] = mergeAccountRecord(item, byUser[key])
continue
}
byUser[key] = item
prevOrder = append(prevOrder, key)
}
out := make([]missionentity.SimilarAccount, 0, len(newOrder)+len(prevOrder))
for _, key := range newOrder {
if acc, ok := byUser[key]; ok {
out = append(out, acc)
}
}
for _, key := range prevOrder {
if acc, ok := byUser[key]; ok {
out = append(out, acc)
}
}
return prioritizePinnedSimilarAccounts(out)
}
func mergeAccountRecord(prev, next missionentity.SimilarAccount) missionentity.SimilarAccount {
out := next
out.MatchedSource = unionMatchedSource(prev.MatchedSource, next.MatchedSource)
switch prev.Status {
case missionentity.SimilarAccountStatusPinned, missionentity.SimilarAccountStatusExcluded:
out.Status = prev.Status
default:
if strings.TrimSpace(out.Status) == "" {
out.Status = missionentity.SimilarAccountStatusRecommended
}
}
return out
}
func prioritizePinnedSimilarAccounts(accounts []missionentity.SimilarAccount) []missionentity.SimilarAccount {
if len(accounts) == 0 {
return nil
}
pinned := make([]missionentity.SimilarAccount, 0)
rest := make([]missionentity.SimilarAccount, 0, len(accounts))
for _, item := range accounts {
if item.Status == missionentity.SimilarAccountStatusPinned {
pinned = append(pinned, item)
} else {
rest = append(rest, item)
}
}
return capSimilarAccounts(append(pinned, rest...))
}
func capSimilarAccounts(accounts []missionentity.SimilarAccount) []missionentity.SimilarAccount {
if len(accounts) <= MaxSimilarAccounts {
return accounts
}
pinned := make([]missionentity.SimilarAccount, 0)
rest := make([]missionentity.SimilarAccount, 0, len(accounts))
for _, item := range accounts {
if item.Status == missionentity.SimilarAccountStatusPinned {
pinned = append(pinned, item)
} else {
rest = append(rest, item)
}
}
remaining := MaxSimilarAccounts - len(pinned)
if remaining < 0 {
return pinned[:MaxSimilarAccounts]
}
if len(rest) > remaining {
rest = rest[:remaining]
}
return append(pinned, rest...)
}
func unionMatchedSource(a, b []string) []string {
if len(a) == 0 && len(b) == 0 {
return nil
}
seen := map[string]struct{}{}
out := make([]string, 0, len(a)+len(b))
for _, item := range append(append([]string{}, a...), b...) {
s := strings.TrimSpace(item)
if s == "" {
continue
}
if _, ok := seen[s]; ok {
continue
}
seen[s] = struct{}{}
out = append(out, s)
}
sort.Strings(out)
if len(out) == 0 {
return nil
}
return out
}
func ExcludedSimilarAccountUsernames(accounts []missionentity.SimilarAccount) []string {
out := make([]string, 0)
seen := map[string]struct{}{}
for _, item := range accounts {
if item.Status != missionentity.SimilarAccountStatusExcluded {
continue
}
user := strings.TrimSpace(item.Username)
if user == "" {
continue
}
key := strings.ToLower(user)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
out = append(out, user)
}
return out
}
func SimilarAccountUsernames(accounts []missionentity.SimilarAccount) []string {
out := make([]string, 0, len(accounts))
seen := map[string]struct{}{}
for _, item := range accounts {
user := strings.TrimSpace(item.Username)
if user == "" {
continue
}
key := strings.ToLower(user)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
out = append(out, user)
}
return out
}
func isExcluded(excluded []string, username string) bool {
user := strings.ToLower(strings.TrimSpace(username))
if user == "" {
return false
}
for _, item := range excluded {
if strings.ToLower(strings.TrimSpace(item)) == user {
return true
}
}
return false
}