client.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package youtube
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "time"
  7. "git.linuxforward.com/byom/byom-trends/config"
  8. "git.linuxforward.com/byom/byom-trends/logger"
  9. "github.com/sirupsen/logrus"
  10. )
  11. type Client struct {
  12. httpClient *http.Client
  13. apiKey string
  14. baseURL string
  15. logger *logrus.Entry
  16. }
  17. type ChannelStats struct {
  18. Username string `json:"username"`
  19. ChannelID string `json:"channel_id"`
  20. SubscriberCount int64 `json:"subscriber_count"`
  21. VideoCount int64 `json:"video_count"`
  22. ViewCount int64 `json:"view_count"`
  23. Engagement float64 `json:"engagement"`
  24. Country string `json:"country"`
  25. JoinDate string `json:"join_date"`
  26. }
  27. type Video struct {
  28. ID string `json:"id"`
  29. Title string `json:"title"`
  30. Description string `json:"description"`
  31. PublishedAt time.Time `json:"published_at"`
  32. Duration string `json:"duration"`
  33. ViewCount int64 `json:"view_count"`
  34. LikeCount int64 `json:"like_count"`
  35. CommentCount int64 `json:"comment_count"`
  36. ThumbnailURL string `json:"thumbnail_url"`
  37. Tags []string `json:"tags"`
  38. Category string `json:"category"`
  39. Language string `json:"language"`
  40. }
  41. type PlaylistStats struct {
  42. ID string `json:"id"`
  43. Title string `json:"title"`
  44. VideoCount int64 `json:"video_count"`
  45. ViewCount int64 `json:"view_count"`
  46. Description string `json:"description"`
  47. }
  48. func NewClient(cfg *config.YouTubeConfig) *Client {
  49. return &Client{
  50. httpClient: &http.Client{
  51. Timeout: time.Second * 10,
  52. },
  53. apiKey: cfg.APIKey,
  54. baseURL: cfg.BaseURL,
  55. logger: logger.NewLogger("youtube-client"),
  56. }
  57. }
  58. func (c *Client) GetChannelStats(channelID string) (*ChannelStats, error) {
  59. url := fmt.Sprintf("%s/channels?part=statistics,snippet&id=%s&key=%s",
  60. c.baseURL, channelID, c.apiKey)
  61. resp, err := c.httpClient.Get(url)
  62. if err != nil {
  63. return nil, fmt.Errorf("do request: %w", err)
  64. }
  65. defer resp.Body.Close()
  66. if resp.StatusCode != http.StatusOK {
  67. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  68. }
  69. var stats ChannelStats
  70. if err := json.NewDecoder(resp.Body).Decode(&stats); err != nil {
  71. return nil, fmt.Errorf("decode response: %w", err)
  72. }
  73. return &stats, nil
  74. }
  75. func (c *Client) GetRecentVideos(channelID string, maxResults int) ([]Video, error) {
  76. url := fmt.Sprintf("%s/search?part=snippet&channelId=%s&order=date&maxResults=%d&key=%s",
  77. c.baseURL, channelID, maxResults, c.apiKey)
  78. resp, err := c.httpClient.Get(url)
  79. if err != nil {
  80. return nil, fmt.Errorf("do request: %w", err)
  81. }
  82. defer resp.Body.Close()
  83. if resp.StatusCode != http.StatusOK {
  84. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  85. }
  86. var videos []Video
  87. if err := json.NewDecoder(resp.Body).Decode(&videos); err != nil {
  88. return nil, fmt.Errorf("decode response: %w", err)
  89. }
  90. return videos, nil
  91. }
  92. func (c *Client) GetVideoStats(videoID string) (*Video, error) {
  93. url := fmt.Sprintf("%s/videos?part=statistics,snippet,contentDetails&id=%s&key=%s",
  94. c.baseURL, videoID, c.apiKey)
  95. resp, err := c.httpClient.Get(url)
  96. if err != nil {
  97. return nil, fmt.Errorf("do request: %w", err)
  98. }
  99. defer resp.Body.Close()
  100. if resp.StatusCode != http.StatusOK {
  101. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  102. }
  103. var video Video
  104. if err := json.NewDecoder(resp.Body).Decode(&video); err != nil {
  105. return nil, fmt.Errorf("decode response: %w", err)
  106. }
  107. return &video, nil
  108. }
  109. func (c *Client) GetPlaylists(channelID string) ([]PlaylistStats, error) {
  110. url := fmt.Sprintf("%s/playlists?part=snippet,contentDetails&channelId=%s&key=%s",
  111. c.baseURL, channelID, c.apiKey)
  112. resp, err := c.httpClient.Get(url)
  113. if err != nil {
  114. return nil, fmt.Errorf("do request: %w", err)
  115. }
  116. defer resp.Body.Close()
  117. if resp.StatusCode != http.StatusOK {
  118. return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  119. }
  120. var playlists []PlaylistStats
  121. if err := json.NewDecoder(resp.Body).Decode(&playlists); err != nil {
  122. return nil, fmt.Errorf("decode response: %w", err)
  123. }
  124. return playlists, nil
  125. }
  126. func (c *Client) CalculateEngagement(videos []Video) float64 {
  127. if len(videos) == 0 {
  128. return 0
  129. }
  130. var totalEngagement int64
  131. for _, video := range videos {
  132. // YouTube engagement includes views, likes, and comments
  133. totalEngagement += video.ViewCount + video.LikeCount + video.CommentCount
  134. }
  135. return float64(totalEngagement) / float64(len(videos))
  136. }
  137. func (c *Client) GetTrendingTags(videos []Video) map[string]int {
  138. tags := make(map[string]int)
  139. for _, video := range videos {
  140. for _, tag := range video.Tags {
  141. tags[tag]++
  142. }
  143. }
  144. return tags
  145. }