Pure parser for watch?v=, youtu.be/, shorts/, embed/, and bare ids; rejects non-YouTube hosts and malformed input. Foundation for paste-a-URL summarize (Feature 2). No fetch, no gate interaction — parsing only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63 lines
1.6 KiB
Go
63 lines
1.6 KiB
Go
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>
|
|
id = strings.Trim(u.Path, "/")
|
|
case u.Path == "/watch":
|
|
id = u.Query().Get("v")
|
|
default:
|
|
// /shorts/<id>, /embed/<id>
|
|
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
|
|
}
|