package main import ( "bufio" "context" "fmt" "log" "os" "regexp" "strings" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/option" "google.golang.org/api/youtube/v3" ) func getClient(config *oauth2.Config) *oauth2.Token { tokFile := "token.json" f, err := os.Open(tokFile) if err == nil { defer f.Close() var tok oauth2.Token fmt.Fscan(f, &tok.AccessToken, &tok.TokenType, &tok.RefreshToken, &tok.Expiry) return &tok } authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) fmt.Printf("Go to the following link in your browser then type the authorization code: \n%v\n", authURL) var authCode string fmt.Scan(&authCode) tok, err := config.Exchange(context.Background(), authCode) if err != nil { log.Fatalf("Unable to retrieve token from web: %v", err) } f, _ = os.Create(tokFile) defer f.Close() fmt.Fprint(f, tok.AccessToken, " ", tok.TokenType, " ", tok.RefreshToken, " ", tok.Expiry) return tok } func main() { b, err := os.ReadFile("credentials.json") if err != nil { log.Fatalf("Unable to read client secret file: %v", err) } config, err := google.ConfigFromJSON(b, youtube.YoutubeScope) if err != nil { log.Fatalf("Unable to parse client secret file to config: %v", err) } tok := getClient(config) client := config.Client(context.Background(), tok) service, err := youtube.NewService(context.Background(), option.WithHTTPClient(client)) if err != nil { log.Fatalf("Unable to create YouTube client: %v", err) } file, err := os.Open("playlist.md") if err != nil { log.Fatalf("Unable to open playlist file: %v", err) } defer file.Close() scanner := bufio.NewScanner(file) linkRegex := regexp.MustCompile(`https:\/\/music\.youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)`) playlists := make(map[string][]string) var currentPlaylist string for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "## ") { currentPlaylist = strings.TrimPrefix(line, "## ") playlists[currentPlaylist] = []string{} } if matches := linkRegex.FindStringSubmatch(line); matches != nil && currentPlaylist != "" { playlists[currentPlaylist] = append(playlists[currentPlaylist], matches[1]) } } if len(playlists) == 0 { log.Fatal("No playlists found in file") } for title, videoIDs := range playlists { if len(videoIDs) == 0 { continue } playlist := &youtube.Playlist{ Snippet: &youtube.PlaylistSnippet{ Title: title, Description: "Created automatically from Markdown", }, Status: &youtube.PlaylistStatus{ PrivacyStatus: "private", }, } newPlaylist, err := service.Playlists.Insert([]string{"snippet", "status"}, playlist).Do() if err != nil { log.Printf("Unable to create playlist '%s': %v", title, err) continue } for _, id := range videoIDs { _, err := service.PlaylistItems.Insert([]string{"snippet"}, &youtube.PlaylistItem{ Snippet: &youtube.PlaylistItemSnippet{ PlaylistId: newPlaylist.Id, ResourceId: &youtube.ResourceId{ Kind: "youtube#video", VideoId: id, }, }, }).Do() if err != nil { log.Printf("Unable to add video %s to playlist '%s': %v", id, title, err) } } fmt.Printf("Playlist '%s' created with %d videos\n", title, len(videoIDs)) } }