package youtube import ( "encoding/json" "fmt" "net/http" "time" "git.linuxforward.com/byom/byom-trends/config" "git.linuxforward.com/byom/byom-trends/logger" "github.com/sirupsen/logrus" ) type Client struct { httpClient *http.Client apiKey string baseURL string logger *logrus.Entry } type ChannelStats struct { Username string `json:"username"` ChannelID string `json:"channel_id"` SubscriberCount int64 `json:"subscriber_count"` VideoCount int64 `json:"video_count"` ViewCount int64 `json:"view_count"` Engagement float64 `json:"engagement"` Country string `json:"country"` JoinDate string `json:"join_date"` } type Video struct { ID string `json:"id"` Title string `json:"title"` Description string `json:"description"` PublishedAt time.Time `json:"published_at"` Duration string `json:"duration"` ViewCount int64 `json:"view_count"` LikeCount int64 `json:"like_count"` CommentCount int64 `json:"comment_count"` ThumbnailURL string `json:"thumbnail_url"` Tags []string `json:"tags"` Category string `json:"category"` Language string `json:"language"` } type PlaylistStats struct { ID string `json:"id"` Title string `json:"title"` VideoCount int64 `json:"video_count"` ViewCount int64 `json:"view_count"` Description string `json:"description"` } func NewClient(cfg *config.YouTubeConfig) *Client { return &Client{ httpClient: &http.Client{ Timeout: time.Second * 10, }, apiKey: cfg.APIKey, baseURL: cfg.BaseURL, logger: logger.NewLogger("youtube-client"), } } func (c *Client) GetChannelStats(channelID string) (*ChannelStats, error) { url := fmt.Sprintf("%s/channels?part=statistics,snippet&id=%s&key=%s", c.baseURL, channelID, c.apiKey) resp, err := c.httpClient.Get(url) if err != nil { return nil, fmt.Errorf("do request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var stats ChannelStats if err := json.NewDecoder(resp.Body).Decode(&stats); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return &stats, nil } func (c *Client) GetRecentVideos(channelID string, maxResults int) ([]Video, error) { url := fmt.Sprintf("%s/search?part=snippet&channelId=%s&order=date&maxResults=%d&key=%s", c.baseURL, channelID, maxResults, c.apiKey) resp, err := c.httpClient.Get(url) if err != nil { return nil, fmt.Errorf("do request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var videos []Video if err := json.NewDecoder(resp.Body).Decode(&videos); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return videos, nil } func (c *Client) GetVideoStats(videoID string) (*Video, error) { url := fmt.Sprintf("%s/videos?part=statistics,snippet,contentDetails&id=%s&key=%s", c.baseURL, videoID, c.apiKey) resp, err := c.httpClient.Get(url) if err != nil { return nil, fmt.Errorf("do request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var video Video if err := json.NewDecoder(resp.Body).Decode(&video); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return &video, nil } func (c *Client) GetPlaylists(channelID string) ([]PlaylistStats, error) { url := fmt.Sprintf("%s/playlists?part=snippet,contentDetails&channelId=%s&key=%s", c.baseURL, channelID, c.apiKey) resp, err := c.httpClient.Get(url) if err != nil { return nil, fmt.Errorf("do request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var playlists []PlaylistStats if err := json.NewDecoder(resp.Body).Decode(&playlists); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return playlists, nil } func (c *Client) CalculateEngagement(videos []Video) float64 { if len(videos) == 0 { return 0 } var totalEngagement int64 for _, video := range videos { // YouTube engagement includes views, likes, and comments totalEngagement += video.ViewCount + video.LikeCount + video.CommentCount } return float64(totalEngagement) / float64(len(videos)) } func (c *Client) GetTrendingTags(videos []Video) map[string]int { tags := make(map[string]int) for _, video := range videos { for _, tag := range video.Tags { tags[tag]++ } } return tags }