resolve_channel_id.go
1538 bytes
1package youtube
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net/http"
9 "net/url"
10)
11
12type apiChannelIDResponse struct {
13 Items []struct {
14 ID string `json:"id"`
15 } `json:"items"`
16}
17
18func (c *youTubeClient) ResolveChannelID(ctx context.Context, handle string) (string, error) {
19 query := url.Values{}
20 query.Add("forHandle", handle)
21 query.Add("part", "id")
22 query.Add("fields", "items/id")
23 query.Add("key", c.apiKey)
24
25 req, err := http.NewRequestWithContext(ctx, http.MethodGet, "", nil)
26 if err != nil {
27 return "", fmt.Errorf("failed to set up request: %w", err)
28 }
29 req.URL = &url.URL{
30 Scheme: "https",
31 Host: "www.googleapis.com",
32 Path: "/youtube/v3/channels",
33 RawQuery: query.Encode(),
34 }
35
36 c.log.Info("Resolving channel handle to ID", "handle", handle)
37 resp, err := http.DefaultClient.Do(req)
38 if err != nil {
39 return "", fmt.Errorf("failed to fetch channel: %w", err)
40 }
41 defer resp.Body.Close()
42
43 if resp.StatusCode != http.StatusOK {
44 body, err := io.ReadAll(resp.Body)
45 var bodyStr string
46 if err != nil {
47 bodyStr = "failed to decode body"
48 } else {
49 bodyStr = string(body)
50 }
51 return "", fmt.Errorf("got non-200 status from YouTube API [%d]: %v", resp.StatusCode, bodyStr)
52 }
53
54 var respData apiChannelIDResponse
55 if err := json.NewDecoder(resp.Body).Decode(&respData); err != nil {
56 return "", fmt.Errorf("failed to decode response: %w", err)
57 }
58
59 if len(respData.Items) == 0 {
60 return "", fmt.Errorf("channel not found for handle %q", handle)
61 }
62
63 return respData.Items[0].ID, nil
64}