324 lines
7.7 KiB
Go
324 lines
7.7 KiB
Go
// Copyright 2025 eat-pray-ai & OpenWaygate
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package playlistImage
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
|
|
"github.com/eat-pray-ai/yutu/pkg"
|
|
"github.com/eat-pray-ai/yutu/pkg/auth"
|
|
"github.com/eat-pray-ai/yutu/pkg/utils"
|
|
"github.com/jedib0t/go-pretty/v6/table"
|
|
"google.golang.org/api/youtube/v3"
|
|
)
|
|
|
|
var (
|
|
service *youtube.Service
|
|
errGetPlaylistImage = errors.New("failed to get playlist image")
|
|
errInsertPlaylistImage = errors.New("failed to insert playlist image")
|
|
errUpdatePlaylistImage = errors.New("failed to update playlist image")
|
|
errDeletePlaylistImage = errors.New("failed to delete playlist image")
|
|
)
|
|
|
|
type playlistImage struct {
|
|
IDs []string `yaml:"ids" json:"ids"`
|
|
Height int64 `yaml:"height" json:"height"`
|
|
PlaylistID string `yaml:"playlistId" json:"playlistId"`
|
|
Type string `yaml:"type" json:"type"`
|
|
Width int64 `yaml:"width" json:"width"`
|
|
File string `yaml:"file" json:"file"`
|
|
|
|
Parent string `yaml:"parent" json:"parent"`
|
|
MaxResults int64 `yaml:"max_results" json:"max_results"`
|
|
|
|
OnBehalfOfContentOwner string `yaml:"on_behalf_of_content_owner" json:"on_behalf_of_content_owner"`
|
|
OnBehalfOfContentOwnerChannel string `yaml:"on_behalf_of_content_owner_channel" json:"on_behalf_of_content_owner_channel"`
|
|
}
|
|
|
|
type PlaylistImage[T any] interface {
|
|
Get([]string) ([]*T, error)
|
|
List([]string, string, string, io.Writer) error
|
|
Insert(string, string, io.Writer) error
|
|
Update(string, string, io.Writer) error
|
|
Delete(io.Writer) error
|
|
}
|
|
|
|
type Option func(*playlistImage)
|
|
|
|
func NewPlaylistImage(opts ...Option) PlaylistImage[youtube.PlaylistImage] {
|
|
pi := &playlistImage{}
|
|
for _, opt := range opts {
|
|
opt(pi)
|
|
}
|
|
return pi
|
|
}
|
|
|
|
func (pi *playlistImage) Get(parts []string) ([]*youtube.PlaylistImage, error) {
|
|
call := service.PlaylistImages.List()
|
|
call = call.Part(parts...)
|
|
if pi.Parent != "" {
|
|
call = call.Parent(pi.Parent)
|
|
}
|
|
if pi.OnBehalfOfContentOwner != "" {
|
|
call = call.OnBehalfOfContentOwner(pi.OnBehalfOfContentOwner)
|
|
}
|
|
if pi.OnBehalfOfContentOwnerChannel != "" {
|
|
call = call.OnBehalfOfContentOwnerChannel(pi.OnBehalfOfContentOwnerChannel)
|
|
}
|
|
|
|
var items []*youtube.PlaylistImage
|
|
pageToken := ""
|
|
for pi.MaxResults > 0 {
|
|
call = call.MaxResults(min(pi.MaxResults, pkg.PerPage))
|
|
pi.MaxResults -= pkg.PerPage
|
|
if pageToken != "" {
|
|
call = call.PageToken(pageToken)
|
|
}
|
|
|
|
res, err := call.Do()
|
|
if err != nil {
|
|
return items, errors.Join(errGetPlaylistImage, err)
|
|
}
|
|
|
|
items = append(items, res.Items...)
|
|
pageToken = res.NextPageToken
|
|
if pageToken == "" || len(res.Items) == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
func (pi *playlistImage) List(
|
|
parts []string, output string, jpath string, writer io.Writer,
|
|
) error {
|
|
playlistImages, err := pi.Get(parts)
|
|
if err != nil && playlistImages == nil {
|
|
return err
|
|
}
|
|
|
|
switch output {
|
|
case "json":
|
|
utils.PrintJSON(playlistImages, jpath, writer)
|
|
case "yaml":
|
|
utils.PrintYAML(playlistImages, jpath, writer)
|
|
case "table":
|
|
tb := table.NewWriter()
|
|
defer tb.Render()
|
|
tb.SetOutputMirror(writer)
|
|
tb.SetStyle(pkg.TableStyle)
|
|
tb.AppendHeader(table.Row{"ID", "Kind", "Playlist ID", "Type"})
|
|
for _, img := range playlistImages {
|
|
tb.AppendRow(
|
|
table.Row{img.Id, img.Kind, img.Snippet.PlaylistId, img.Snippet.Type},
|
|
)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (pi *playlistImage) Insert(
|
|
output string, jpath string, writer io.Writer,
|
|
) error {
|
|
file, err := pkg.Root.Open(pi.File)
|
|
if err != nil {
|
|
return errors.Join(errInsertPlaylistImage, err)
|
|
}
|
|
defer func(file *os.File) {
|
|
_ = file.Close()
|
|
}(file)
|
|
|
|
playlistImage := &youtube.PlaylistImage{
|
|
Kind: "youtube#playlistImages",
|
|
Snippet: &youtube.PlaylistImageSnippet{
|
|
PlaylistId: pi.PlaylistID,
|
|
Type: pi.Type,
|
|
Height: pi.Height,
|
|
Width: pi.Width,
|
|
},
|
|
}
|
|
|
|
call := service.PlaylistImages.Insert(playlistImage)
|
|
if pi.OnBehalfOfContentOwner != "" {
|
|
call = call.OnBehalfOfContentOwner(pi.OnBehalfOfContentOwner)
|
|
}
|
|
if pi.OnBehalfOfContentOwnerChannel != "" {
|
|
call = call.OnBehalfOfContentOwnerChannel(pi.OnBehalfOfContentOwnerChannel)
|
|
}
|
|
call = call.Media(file)
|
|
call = call.Part("kind", "snippet")
|
|
res, err := call.Do()
|
|
if err != nil {
|
|
return errors.Join(errInsertPlaylistImage, err)
|
|
}
|
|
|
|
switch output {
|
|
case "json":
|
|
utils.PrintJSON(res, jpath, writer)
|
|
case "yaml":
|
|
utils.PrintYAML(res, jpath, writer)
|
|
case "silent":
|
|
default:
|
|
_, _ = fmt.Fprintf(writer, "PlaylistImage inserted: %s\n", res.Id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (pi *playlistImage) Update(
|
|
output string, jpath string, writer io.Writer,
|
|
) error {
|
|
playlistImages, err := pi.Get([]string{"id", "kind", "snippet"})
|
|
if err != nil {
|
|
return errors.Join(errUpdatePlaylistImage, err)
|
|
}
|
|
if len(playlistImages) == 0 {
|
|
return errGetPlaylistImage
|
|
}
|
|
|
|
playlistImage := playlistImages[0]
|
|
if pi.PlaylistID != "" {
|
|
playlistImage.Snippet.PlaylistId = pi.PlaylistID
|
|
}
|
|
if pi.Type != "" {
|
|
playlistImage.Snippet.Type = pi.Type
|
|
}
|
|
if pi.Height != 0 {
|
|
playlistImage.Snippet.Height = pi.Height
|
|
}
|
|
if pi.Width != 0 {
|
|
playlistImage.Snippet.Width = pi.Width
|
|
}
|
|
|
|
call := service.PlaylistImages.Update(playlistImage)
|
|
if pi.OnBehalfOfContentOwner != "" {
|
|
call = call.OnBehalfOfContentOwner(pi.OnBehalfOfContentOwner)
|
|
}
|
|
if pi.File != "" {
|
|
file, err := pkg.Root.Open(pi.File)
|
|
if err != nil {
|
|
return errors.Join(errUpdatePlaylistImage, err)
|
|
}
|
|
defer func(file *os.File) {
|
|
_ = file.Close()
|
|
}(file)
|
|
call = call.Media(file)
|
|
}
|
|
call = call.Part("id", "kind", "snippet")
|
|
|
|
res, err := call.Do()
|
|
if err != nil {
|
|
return errors.Join(errUpdatePlaylistImage, err)
|
|
}
|
|
|
|
switch output {
|
|
case "json":
|
|
utils.PrintJSON(res, jpath, writer)
|
|
case "yaml":
|
|
utils.PrintYAML(res, jpath, writer)
|
|
case "silent":
|
|
default:
|
|
_, _ = fmt.Fprintf(writer, "PlaylistImage updated: %s\n", res.Id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (pi *playlistImage) Delete(writer io.Writer) error {
|
|
for _, id := range pi.IDs {
|
|
call := service.PlaylistImages.Delete()
|
|
call = call.Id(id)
|
|
if pi.OnBehalfOfContentOwner != "" {
|
|
call = call.OnBehalfOfContentOwner(pi.OnBehalfOfContentOwner)
|
|
}
|
|
|
|
err := call.Do()
|
|
if err != nil {
|
|
return errors.Join(errDeletePlaylistImage, err)
|
|
}
|
|
_, _ = fmt.Fprintf(writer, "PlaylistImage %s deleted\n", id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func WithIDs(ids []string) Option {
|
|
return func(pi *playlistImage) {
|
|
pi.IDs = ids
|
|
}
|
|
}
|
|
|
|
func WithHeight(height int64) Option {
|
|
return func(pi *playlistImage) {
|
|
pi.Height = height
|
|
}
|
|
}
|
|
|
|
func WithPlaylistID(playlistID string) Option {
|
|
return func(pi *playlistImage) {
|
|
pi.PlaylistID = playlistID
|
|
}
|
|
}
|
|
|
|
func WithType(t string) Option {
|
|
return func(pi *playlistImage) {
|
|
pi.Type = t
|
|
}
|
|
}
|
|
|
|
func WithWidth(width int64) Option {
|
|
return func(pi *playlistImage) {
|
|
pi.Width = width
|
|
}
|
|
}
|
|
|
|
func WithFile(file string) Option {
|
|
return func(pi *playlistImage) {
|
|
pi.File = file
|
|
}
|
|
}
|
|
|
|
func WithParent(parent string) Option {
|
|
return func(pi *playlistImage) {
|
|
pi.Parent = parent
|
|
}
|
|
}
|
|
|
|
func WithMaxResults(maxResults int64) Option {
|
|
return func(pi *playlistImage) {
|
|
if maxResults < 0 {
|
|
maxResults = 1
|
|
} else if maxResults == 0 {
|
|
maxResults = math.MaxInt64
|
|
}
|
|
pi.MaxResults = maxResults
|
|
}
|
|
}
|
|
|
|
func WithOnBehalfOfContentOwner(onBehalfOfContentOwner string) Option {
|
|
return func(pi *playlistImage) {
|
|
pi.OnBehalfOfContentOwner = onBehalfOfContentOwner
|
|
}
|
|
}
|
|
|
|
func WithOnBehalfOfContentOwnerChannel(onBehalfOfContentOwnerChannel string) Option {
|
|
return func(pi *playlistImage) {
|
|
pi.OnBehalfOfContentOwnerChannel = onBehalfOfContentOwnerChannel
|
|
}
|
|
}
|
|
|
|
func WithService(svc *youtube.Service) Option {
|
|
return func(pi *playlistImage) {
|
|
if svc == nil {
|
|
svc = auth.NewY2BService(
|
|
auth.WithCredential("", pkg.Root.FS()),
|
|
auth.WithCacheToken("", pkg.Root.FS()),
|
|
).GetService()
|
|
}
|
|
service = svc
|
|
}
|
|
}
|