src/music/video.go (view raw)
1package music
2
3import (
4 "errors"
5 "regexp"
6 "strings"
7 "time"
8
9 "github.com/birabittoh/rabbitpipe"
10)
11
12const (
13 defaultCacheDuration = 6 * time.Hour
14)
15
16var (
17 expireRegex = regexp.MustCompile(`(?i)expire=(\d+)`)
18 videoRegexpList = []*regexp.Regexp{ // from github.com/kkdai/youtube
19 regexp.MustCompile(`(?:v|embed|shorts|watch\?v)(?:=|/)([^"&?/=%]{11})`),
20 regexp.MustCompile(`(?:=|/)([^"&?/=%]{11})`),
21 regexp.MustCompile(`([^"&?/=%]{11})`),
22 }
23)
24
25func extractVideoID(videoID string) (string, error) {
26 if strings.Contains(videoID, "youtu") || strings.ContainsAny(videoID, "\"?&/<%=") {
27 for _, re := range videoRegexpList {
28 if isMatch := re.MatchString(videoID); isMatch {
29 subs := re.FindStringSubmatch(videoID)
30 videoID = subs[1]
31 }
32 }
33 }
34
35 if strings.ContainsAny(videoID, "?&/<%=") {
36 return "", errors.New("invalid characters in videoID")
37 }
38
39 if len(videoID) < 10 {
40 return "", errors.New("videoID is too short")
41 }
42
43 return videoID, nil
44}
45
46func getFormat(video rabbitpipe.Video) *rabbitpipe.AdaptiveFormat {
47 formats := video.AdaptiveFormats
48 for i, format := range formats {
49 if format.URL != "" && format.AudioChannels > 0 {
50 return &formats[i]
51 }
52 }
53
54 return nil
55}
56
57func getFromYT(videoID string) (video *rabbitpipe.Video, err error) {
58 video, err = yt.GetVideo(videoID)
59 if err != nil || video == nil {
60 logger.Error("Error fetching video info:", err)
61 return nil, errors.New("error fetching video info")
62 }
63
64 format := getFormat(*video)
65 if format == nil {
66 logger.Errorf("no audio formats available for video %s", videoID)
67 return nil, errors.New("no audio formats available")
68 }
69
70 return
71}
72
73func search(query string) (videoID string, err error) {
74 results, err := yt.Search(query)
75 if err == nil && results != nil {
76 for _, result := range *results {
77 if result.Type == "video" && !result.LiveNow && !result.IsUpcoming && !result.Premium {
78 logger.Printf("Video found by API.")
79
80 return result.VideoID, nil
81 }
82 }
83 err = errors.New("search did not return any valid videos")
84 }
85
86 return "", err
87}
88
89func getVideo(args []string) (*rabbitpipe.Video, error) {
90 videoID, err := extractVideoID(args[0])
91 if err != nil {
92 videoID, err = search(strings.Join(args, " "))
93 if err != nil || videoID == "" {
94 return nil, err
95 }
96 }
97
98 return getFromYT(videoID)
99}