1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
/* function to make an api request */
const youtubeAPI = "https://www.googleapis.com/youtube/v3/"
func apiRequest[T any](videoId string, endpoint string, videoIdParam string, part string) (T, error) {
var zero T
if apiKey == "" {
return zero, fmt.Errorf("API_KEY environment variable not set")
}
url := fmt.Sprintf("%s?part=%s&%s=%s&key=%s", youtubeAPI + endpoint, part, videoIdParam, videoId, apiKey)
resp, err := http.Get(url)
if err != nil {
return zero, fmt.Errorf("Failed to fetch video info: " + err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return zero, fmt.Errorf("YouTube API error: " + resp.Status)
}
var data T
err = json.NewDecoder(resp.Body).Decode(&data)
if err != nil {
return zero, fmt.Errorf("Failed to decode YouTube API response: " + err.Error())
}
return data, nil
}
/* youtube api types */
type ytVideoDetails struct {
Items []struct {
Snippet struct {
Channel string `json:"channel"`
Description string `json:"description"`
PublishedAt time.Time `json:"publishedAt"`
Tags []string `json:"tags"`
} `json:"snippet"`
Statistics struct {
CommentCount int `json:"commentCount"`
LikeCount int `json:"likeCount"`
ViewCount int `json:"viewCount"`
} `json:"statistics"`
} `json:"items"`
}
type ytComment struct {
Snippet struct {
AuthorDisplayName string `json:"authorDisplayName"`
TextDisplay string `json:"textDisplay"`
PublishedAt time.Time `json:"publishedAt"`
UpdatedAt time.Time `json:"updatedAt"`
LikeCount int `json:"likeCount"`
} `json:"snippet"`
}
type ytComments struct {
Items []struct {
Snippet struct {
TopLevelComment ytComment `json:"topLevelComment"`
} `json:"snippet"`
Replies struct {
Comments []ytComment `json:"comments,omitempty"`
} `json:"replies"`
} `json:"items"`
}
/* api source interface definition */
type APISourceOfficial struct {}
func (a *APISourceOfficial) getDetails(videoId string) (VideoDetails, error) {
d, err := apiRequest[ytVideoDetails](videoId, "videos", "id", "snippet,statistics,topicDetails")
n := d.Items[0].Snippet
t := d.Items[0].Statistics
return VideoDetails{
Channel: n.Channel,
DatePublished: n.PublishedAt,
Description: n.Description,
NumComments: t.CommentCount,
NumLikes: t.LikeCount,
NumViews: t.ViewCount,
Tags: n.Tags,
}, err
}
func genComment(c ytComment, r []ytComment) Comment {
n := c.Snippet
var replies []*Comment
for _, rc := range r {
temp := genComment(rc, []ytComment{}) // NOTE temp variable required here
replies = append(replies, &temp) // cannot reference return value of genComment w/o temp var
}
return Comment{
Author: n.AuthorDisplayName,
Body: n.TextDisplay,
DatePublished: n.PublishedAt,
DateUpdated: n.UpdatedAt,
Likes: n.LikeCount,
Replies: replies,
}
}
func (a *APISourceOfficial) getComments(videoId string) ([]Comment, error) {
const maxResults = 100
d, err := apiRequest[ytComments](videoId, "commentThreads", "videoId", "snippet,replies&maxResults=" + fmt.Sprint(maxResults)) // TODO configure max results
var comments []Comment
for _, c := range d.Items {
tlc := c.Snippet.TopLevelComment
r := c.Replies.Comments
comments = append(comments, genComment(tlc, r))
}
return comments, err
}
|