all repos — disgord @ 585845a7da99f6478c85019d190eb7f9446c55e4

A simple Discord bot in Go.

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}