plugins/youtube.go (view raw)
1package plugins
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "net/url"
8 "strings"
9 "time"
10
11 "git.icyphox.sh/paprika/config"
12 "github.com/dustin/go-humanize"
13 "google.golang.org/api/option"
14 "google.golang.org/api/youtube/v3"
15 "gopkg.in/irc.v3"
16)
17
18type Youtube struct{}
19
20func (Youtube) Triggers() []string {
21 return []string{".yt"}
22}
23
24func (Youtube) Execute(m *irc.Message) (string, error) {
25 parsed := strings.SplitN(m.Trailing(), " ", 2)
26 if len(parsed) == 1 && parsed[0] == ".yt" {
27 return "Usage: .yt QUERY", nil
28 } else if parsed[0] != ".yt" {
29 return "", NoReply // ???
30 }
31
32 return YoutubeSearch(parsed[1])
33}
34
35func init() {
36 Register(Youtube{})
37}
38
39var NoYtApiKey = errors.New("No Youtube Api Key")
40
41func constructApiService() (*youtube.Service, error) {
42 if apiKey, ok := config.C.ApiKeys["youtube"]; ok {
43 service, err := youtube.NewService(
44 context.TODO(),
45 option.WithAPIKey(apiKey),
46 option.WithUserAgent("github.com/icyphox/paprika"),
47 option.WithTelemetryDisabled(), // wtf?
48 )
49 return service, err
50 } else {
51 return nil, NoYtApiKey
52 }
53}
54
55func YoutubeSearch(query string) (string, error) {
56 service, err := constructApiService()
57 if err != nil {
58 return "", err
59 }
60
61 search := youtube.NewSearchService(service).List([]string{})
62 search = search.Q(query)
63 res, err := search.Do()
64 if err != nil {
65 return "", err
66 }
67
68 if len(res.Items) == 0 ||
69 res.Items[0].Id == nil ||
70 res.Items[0].Id.VideoId == "" {
71 return "[Youtube] No videos found.", nil
72 }
73 item := res.Items[0]
74
75 vid := item.Id.VideoId
76 println(vid)
77 description, err := YoutubeDescription(vid)
78 if err != nil {
79 return description, err
80 }
81
82 return fmt.Sprintf("%s - https://youtu.be/%s", description, vid), nil
83}
84
85func YoutubeDescriptionFromUrl(url *url.URL) (string, error) {
86 var vid string
87 if url.Host == "youtu.be" {
88 vid = url.Path[1:]
89 } else {
90 vid = url.Query().Get("v")
91 }
92
93 return YoutubeDescription(vid)
94}
95
96func YoutubeDescription(vid string) (string, error) {
97 if vid == "" {
98 return "[Youtube] Could not find video id.", nil
99 }
100
101 service, err := constructApiService()
102 if err != nil {
103 return "", err
104 }
105
106 vidservice := youtube.NewVideosService(service)
107 vcall := vidservice.List([]string{"snippet", "statistics", "contentDetails"})
108 vcall = vcall.Id(vid)
109 vres, err := vcall.Do()
110 if err != nil {
111 return "", err
112 }
113
114 if len(vres.Items) == 0 {
115 return "[Youtube] No video found", nil
116 }
117
118 snippet := vres.Items[0]
119 if snippet.Snippet == nil || snippet.ContentDetails == nil || snippet.Statistics == nil {
120 return "[Youtube] API Error. Required fields are nil.", nil
121 }
122
123 title := snippet.Snippet.Title
124 duration := strings.ToLower(snippet.ContentDetails.Duration[2:])
125 likes := humanize.Comma(int64(snippet.Statistics.LikeCount))
126 // disabled
127 // dislikes := humanize.Comma(int64(snippet.Statistics.DislikeCount))
128 views := humanize.Comma(int64(snippet.Statistics.ViewCount))
129 channelName := snippet.Snippet.ChannelTitle
130 publishedParsed, err := time.Parse(time.RFC3339, snippet.Snippet.PublishedAt)
131 var published string
132 if err == nil {
133 published = humanize.Time(publishedParsed)
134 } else {
135 published = snippet.Snippet.PublishedAt[:10]
136 }
137
138 return fmt.Sprintf(
139 "\x02%s\x02 %s - \x0303↑ %s\x03 \x0304↓ ?\x03 - \x02%s\x02 views - \x02%s\x02 %s",
140 title,
141 duration,
142 likes,
143 views,
144 channelName,
145 published,
146 ), nil
147}