191 lines
4.9 KiB
Go
191 lines
4.9 KiB
Go
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
|
|
}
|