123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- package analysis
- import (
- "context"
- "encoding/json"
- "fmt"
- "time"
- "git.linuxforward.com/byom/byom-trends/common"
- "git.linuxforward.com/byom/byom-trends/logger"
- "git.linuxforward.com/byom/byom-trends/services/google"
- "git.linuxforward.com/byom/byom-trends/services/instagram"
- "git.linuxforward.com/byom/byom-trends/services/tiktok"
- "git.linuxforward.com/byom/byom-trends/services/youtube"
- "github.com/sirupsen/logrus"
- )
- type TrendAnalyzer struct {
- instagramSvc *instagram.Client
- tiktokSvc *tiktok.Client
- youtubeSvc *youtube.Client
- googleSvc *google.Client
- logger *logrus.Entry
- }
- type EngagementMetrics struct {
- Platform string `json:"platform"`
- Engagement float64 `json:"engagement"`
- PostCount int64 `json:"post_count"`
- Followers int64 `json:"followers"`
- TotalLikes int64 `json:"total_likes"`
- TotalViews int64 `json:"total_views"`
- GrowthRate float64 `json:"growth_rate"`
- }
- type ContentTrends struct {
- TopHashtags map[string]int `json:"top_hashtags"`
- TopKeywords map[string]int `json:"top_keywords"`
- PopularTopics []string `json:"popular_topics"`
- BestPerforming map[string]string `json:"best_performing"` // platform -> content ID
- PostingTimes map[string]int `json:"posting_times"` // hour -> count
- }
- type AudienceMetrics struct {
- Demographics map[string]float64 `json:"demographics"`
- Interests []string `json:"interests"`
- Locations map[string]int `json:"locations"`
- ActiveHours map[int]int `json:"active_hours"`
- }
- func NewTrendAnalyzer(
- instagramSvc *instagram.Client,
- tiktokSvc *tiktok.Client,
- youtubeSvc *youtube.Client,
- googleSvc *google.Client,
- ) *TrendAnalyzer {
- return &TrendAnalyzer{
- instagramSvc: instagramSvc,
- tiktokSvc: tiktokSvc,
- youtubeSvc: youtubeSvc,
- googleSvc: googleSvc,
- logger: logger.NewLogger("trend-analyzer"),
- }
- }
- func (a *TrendAnalyzer) AnalyzeEngagement(ctx context.Context, profile *common.SocialProfile) (*EngagementMetrics, error) {
- metrics := &EngagementMetrics{
- Platform: profile.Platform,
- }
- switch profile.Platform {
- case "instagram":
- stats, err := a.instagramSvc.GetProfileStats(profile.Username)
- if err != nil {
- return nil, fmt.Errorf("get instagram stats: %w", err)
- }
- posts, err := a.instagramSvc.GetRecentPosts(profile.Username, 50)
- if err != nil {
- return nil, fmt.Errorf("get instagram posts: %w", err)
- }
- metrics.Engagement = a.instagramSvc.CalculateEngagement(posts)
- metrics.Followers = stats.FollowerCount
- case "tiktok":
- stats, err := a.tiktokSvc.GetProfileStats(profile.Username)
- if err != nil {
- return nil, fmt.Errorf("get tiktok stats: %w", err)
- }
- videos, err := a.tiktokSvc.GetRecentVideos(profile.Username, 50)
- if err != nil {
- return nil, fmt.Errorf("get tiktok videos: %w", err)
- }
- metrics.Engagement = a.tiktokSvc.CalculateEngagement(videos)
- metrics.Followers = stats.FollowerCount
- case "youtube":
- stats, err := a.youtubeSvc.GetChannelStats(profile.Username)
- if err != nil {
- return nil, fmt.Errorf("get youtube stats: %w", err)
- }
- videos, err := a.youtubeSvc.GetRecentVideos(profile.Username, 50)
- if err != nil {
- return nil, fmt.Errorf("get youtube videos: %w", err)
- }
- metrics.Engagement = a.youtubeSvc.CalculateEngagement(videos)
- metrics.Followers = stats.SubscriberCount
- }
- return metrics, nil
- }
- func (a *TrendAnalyzer) AnalyzeContent(ctx context.Context, profile *common.SocialProfile) (*ContentTrends, error) {
- trends := &ContentTrends{
- TopHashtags: make(map[string]int),
- TopKeywords: make(map[string]int),
- PostingTimes: make(map[string]int),
- BestPerforming: make(map[string]string),
- }
- switch profile.Platform {
- case "instagram":
- posts, err := a.instagramSvc.GetRecentPosts(profile.Username, 100)
- if err != nil {
- return nil, fmt.Errorf("get instagram posts: %w", err)
- }
- // Analyze posting times
- for _, post := range posts {
- hour := post.Timestamp.Format("15")
- trends.PostingTimes[hour]++
- }
- case "tiktok":
- videos, err := a.tiktokSvc.GetRecentVideos(profile.Username, 100)
- if err != nil {
- return nil, fmt.Errorf("get tiktok videos: %w", err)
- }
- trends.TopHashtags = a.tiktokSvc.GetTrendingHashtags(videos)
- case "youtube":
- videos, err := a.youtubeSvc.GetRecentVideos(profile.Username, 100)
- if err != nil {
- return nil, fmt.Errorf("get youtube videos: %w", err)
- }
- trends.TopHashtags = a.youtubeSvc.GetTrendingTags(videos)
- }
- // Get related topics from Google Trends
- topics, err := a.googleSvc.GetRelatedTopics(profile.Username)
- if err != nil {
- a.logger.WithError(err).Warn("failed to get related topics")
- } else {
- for _, topic := range topics {
- trends.PopularTopics = append(trends.PopularTopics, topic.Title)
- }
- }
- return trends, nil
- }
- func (a *TrendAnalyzer) AnalyzeAudience(ctx context.Context, profile *common.SocialProfile) (*AudienceMetrics, error) {
- metrics := &AudienceMetrics{
- Demographics: make(map[string]float64),
- Locations: make(map[string]int),
- ActiveHours: make(map[int]int),
- }
- // Get regional interest from Google Trends
- locations, err := a.googleSvc.GetRegionalInterest(profile.Username, "")
- if err != nil {
- a.logger.WithError(err).Warn("failed to get regional interest")
- } else {
- metrics.Locations = locations
- }
- // Analyze active hours based on content posting times and engagement
- switch profile.Platform {
- case "instagram":
- posts, err := a.instagramSvc.GetRecentPosts(profile.Username, 100)
- if err != nil {
- return nil, fmt.Errorf("get instagram posts: %w", err)
- }
- for _, post := range posts {
- hour := post.Timestamp.Hour()
- metrics.ActiveHours[hour]++
- }
- case "tiktok":
- videos, err := a.tiktokSvc.GetRecentVideos(profile.Username, 100)
- if err != nil {
- return nil, fmt.Errorf("get tiktok videos: %w", err)
- }
- for _, video := range videos {
- hour := video.CreateTime.Hour()
- metrics.ActiveHours[hour]++
- }
- case "youtube":
- videos, err := a.youtubeSvc.GetRecentVideos(profile.Username, 100)
- if err != nil {
- return nil, fmt.Errorf("get youtube videos: %w", err)
- }
- for _, video := range videos {
- hour := video.PublishedAt.Hour()
- metrics.ActiveHours[hour]++
- }
- }
- return metrics, nil
- }
- func (a *TrendAnalyzer) GenerateReport(ctx context.Context, profile *common.SocialProfile) (*common.TrendAnalysis, error) {
- engagement, err := a.AnalyzeEngagement(ctx, profile)
- if err != nil {
- return nil, fmt.Errorf("analyze engagement: %w", err)
- }
- content, err := a.AnalyzeContent(ctx, profile)
- if err != nil {
- return nil, fmt.Errorf("analyze content: %w", err)
- }
- audience, err := a.AnalyzeAudience(ctx, profile)
- if err != nil {
- return nil, fmt.Errorf("analyze audience: %w", err)
- }
- // Combine all analyses into a single report
- report := map[string]interface{}{
- "engagement": engagement,
- "content": content,
- "audience": audience,
- "timestamp": time.Now(),
- }
- reportJSON, err := json.Marshal(report)
- if err != nil {
- return nil, fmt.Errorf("marshal report: %w", err)
- }
- analysis := &common.TrendAnalysis{
- Type: "comprehensive",
- Data: reportJSON,
- }
- return analysis, nil
- }
|