-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathurl_youtube.go
145 lines (116 loc) · 3.6 KB
/
url_youtube.go
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package url
import (
"fmt"
"net/url"
"strings"
duration "github.com/channelmeter/iso8601duration"
"github.com/seabird-chat/seabird-go/pb"
"github.com/seabird-irc/seabird-url-plugin/internal"
)
var youtubePrefix = "[YouTube]"
// videos was converted using https://github.com/ChimeraCoder/gojson
type ytVideos struct {
Items []struct {
ContentDetails struct {
Duration string `json:"duration"`
} `json:"contentDetails"`
Snippet struct {
ChannelTitle string `json:"channelTitle"`
Description string `json:"description"`
LiveBroadcastContent string `json:"liveBroadcastContent"`
Title string `json:"title"`
} `json:"snippet"`
} `json:"items"`
}
func NewYoutubeProvider(token string) *YoutubeProvider {
return &YoutubeProvider{token: token}
}
type YoutubeProvider struct {
token string
}
func (p *YoutubeProvider) GetCallbacks() map[string]URLCallback {
return map[string]URLCallback{
"youtube.com": p.handle,
"m.youtube.com": p.handle,
"youtu.be": p.handle,
"music.youtube.com": p.handle,
}
}
func (p *YoutubeProvider) GetMessageCallback() MessageCallback {
return nil
}
func (p *YoutubeProvider) handle(c *Client, source *pb.ChannelSource, req *url.URL) bool {
// Get the Video ID from the URL
values, _ := url.ParseQuery(req.RawQuery)
var id string
if len(values["v"]) > 0 {
// using full www.youtube.com/?v=bbq
id = values["v"][0]
} else {
// using short youtu.be/bbq
path := strings.Split(req.Path, "/")
if len(path) < 1 {
return false
}
id = path[1]
}
// Get video duration and title
time, title := getVideo(id, p.token)
// Invalid video ID or no results
if time == "" && title == "" {
return false
}
c.Replyf(source, "%s %s ~ %s", youtubePrefix, time, title)
return true
}
func getVideo(id string, key string) (time string, title string) {
// Build the API call
api := fmt.Sprintf("https://www.googleapis.com/youtube/v3/videos?part=contentDetails%%2Csnippet&id=%s&fields=items(contentDetails%%2Csnippet)&key=%s", id, key)
var videos ytVideos
if err := internal.GetJSON(api, &videos); err != nil {
return "", ""
}
// Make sure we found a video
if len(videos.Items) < 1 {
return "", ""
}
v := videos.Items[0]
// If it looks like it could be from YT Music, attempt to parse it that way.
// There's a really crappy auto-generated Description field which has all
// the info we need, we just need to parse it out.
if strings.HasSuffix(v.Snippet.Description, "\n\nAuto-generated by YouTube.") {
lines := strings.Split(v.Snippet.Description, "\n\n")
if len(lines) > 3 {
artists := strings.Split(lines[1], " · ")
// The first portion of what we're calling "artists" seems to be the
// track title, so we skip it.
if len(artists) > 0 && artists[0] == v.Snippet.Title {
artists = artists[1:]
}
title = fmt.Sprintf("%q from %s by %s", v.Snippet.Title, lines[2], strings.Join(artists, ", "))
}
}
if title == "" {
title = v.Snippet.Title + " by " + v.Snippet.ChannelTitle
}
switch v.Snippet.LiveBroadcastContent {
case "live", "upcoming":
return strings.Title(v.Snippet.LiveBroadcastContent), title
}
// Convert duration from ISO8601
d, err := duration.FromString(v.ContentDetails.Duration)
if err != nil {
return "", ""
}
var dr string
// Print Days and Hours only if they're not 0
//nolint:gocritic
if d.Days > 0 {
dr = fmt.Sprintf("%02d:%02d:%02d:%02d", d.Days, d.Hours, d.Minutes, d.Seconds)
} else if d.Hours > 0 {
dr = fmt.Sprintf("%02d:%02d:%02d", d.Hours, d.Minutes, d.Seconds)
} else {
dr = fmt.Sprintf("%02d:%02d", d.Minutes, d.Seconds)
}
return dr, title
}