package services import ( "fmt" "git.solsynth.dev/hypernet/interactive/pkg/internal/database" "git.solsynth.dev/hypernet/interactive/pkg/internal/models" "git.solsynth.dev/hypernet/interactive/pkg/internal/services/mastodon" "github.com/rs/zerolog/log" "github.com/samber/lo" "github.com/spf13/viper" "gorm.io/gorm/clause" ) type FromFediversePost interface { ToFediversePost() models.FediversePost } type FediverseFriendConfig struct { ID string `json:"id"` URL string `json:"url"` Type string `json:"type"` Trending bool `json:"trending"` } var fediverseFriends []FediverseFriendConfig func ReadFriendConfig() { if err := viper.UnmarshalKey("fediverse.friends", &fediverseFriends); err != nil { log.Error().Err(err).Msg("Failed to loading fediverse friend config...") } log.Info().Int("count", len(fediverseFriends)).Msg("Loaded fediverse friend config!") } func FetchFediversePost(cfg FediverseFriendConfig) ([]models.FediversePost, error) { switch cfg.Type { case "mastodon": data, err := mastodon.FetchTimeline(cfg.URL, 50, cfg.Trending) if err != nil { return nil, err } posts := lo.Map(data, func(item mastodon.MastodonPost, _ int) models.FediversePost { return item.ToFediversePost() }) return posts, nil default: // TODO Other platform fetching is still under development // DO NOT USE THEM return nil, fmt.Errorf("unsupported fediverse service: %s", cfg.Type) } } func FetchFediverseTimedTask() { if len(fediverseFriends) == 0 { return } log.Debug().Msg("Starting fetching fediverse friends timeline...") var totalPosts []models.FediversePost var totalUsers []models.FediverseUser userMap := make(map[string]models.FediverseUser) for _, friend := range fediverseFriends { log.Info().Str("id", friend.ID).Str("url", friend.URL).Msg("Fetching fediverse friend timeline...") posts, err := FetchFediversePost(friend) if err != nil { log.Error().Err(err).Str("id", friend.ID).Str("url", friend.URL).Msg("Failed to fetch fediverse friend timeline...") continue } log.Info().Str("id", friend.ID).Str("url", friend.URL).Int("count", len(posts)).Msg("Fetched fediverse friend timeline...") for _, post := range posts { if _, exists := userMap[post.User.Identifier]; !exists { userMap[post.User.Identifier] = post.User } } totalPosts = append(totalPosts, posts...) } for _, user := range userMap { totalUsers = append(totalUsers, user) } if len(totalUsers) > 0 { if err := database.C.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "identifier"}}, DoUpdates: clause.AssignmentColumns([]string{"name", "nick", "avatar"}), }).Create(&totalUsers).Error; err != nil { log.Error().Err(err).Msg("Failed to save fediverse users...") } for _, user := range totalUsers { userMap[user.Identifier] = user } } for i := range totalPosts { if user, exists := userMap[totalPosts[i].User.Identifier]; exists { totalPosts[i].UserID = user.ID totalPosts[i].User = user } else { log.Warn().Str("user_identifier", totalPosts[i].User.Identifier).Msg("User ID not found for post, skipping") totalPosts = append(totalPosts[:i], totalPosts[i+1:]...) // Remove invalid post i-- // Adjust index after removal } } if len(totalPosts) > 0 { if err := database.C.Clauses(clause.OnConflict{DoNothing: true}).Create(&totalPosts).Error; err != nil { log.Error().Err(err).Msg("Failed to save timeline posts...") } } }