package web import ( "fmt" "net/url" "regexp" "strings" ) // youtubeVideoID matches a canonical YouTube video id: exactly 11 URL-safe chars. var youtubeVideoID = regexp.MustCompile(`^[A-Za-z0-9_-]{11}$`) // parseYouTubeVideoID extracts the 11-character video id from a pasted YouTube // URL (watch?v=, youtu.be/, shorts/, embed/) or a bare id. It rejects non-YouTube // hosts and anything that doesn't yield a valid id, so the paste flow never tries // to fetch a video that can't exist (Feature 2). func parseYouTubeVideoID(raw string) (string, error) { s := strings.TrimSpace(raw) if s == "" { return "", fmt.Errorf("empty input") } // Bare id (no URL) — accept directly. if youtubeVideoID.MatchString(s) { return s, nil } // Accept scheme-less URLs (youtube.com/watch?v=...) by giving url.Parse a host. if !strings.Contains(s, "://") { s = "https://" + s } u, err := url.Parse(s) if err != nil { return "", fmt.Errorf("not a URL: %w", err) } host := strings.ToLower(u.Hostname()) isYouTube := host == "youtu.be" || host == "youtube.com" || strings.HasSuffix(host, ".youtube.com") if !isYouTube { return "", fmt.Errorf("not a YouTube URL: %q", host) } var id string switch { case host == "youtu.be": // youtu.be/ id = strings.Trim(u.Path, "/") case u.Path == "/watch": id = u.Query().Get("v") default: // /shorts/, /embed/ parts := strings.Split(strings.Trim(u.Path, "/"), "/") if len(parts) == 2 && (parts[0] == "shorts" || parts[0] == "embed") { id = parts[1] } } if !youtubeVideoID.MatchString(id) { return "", fmt.Errorf("no YouTube video id in %q", raw) } return id, nil }