Implement parameter support for /ytdlp command

Co-authored-by: krau <71133316+krau@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-19 04:26:37 +00:00
co-authored by krau
parent bd70160555
commit 9ee9972dec
9 changed files with 66 additions and 28 deletions
+1 -1
View File
@@ -100,7 +100,7 @@ func handleAddCallback(ctx *ext.Context, update *ext.Update) error {
} }
shortcut.CreateAndAddAria2TaskWithEdit(ctx, selectedStorage, dirPath, data.Aria2URIs, client, msgID, userID) shortcut.CreateAndAddAria2TaskWithEdit(ctx, selectedStorage, dirPath, data.Aria2URIs, client, msgID, userID)
case tasktype.TaskTypeYtdlp: case tasktype.TaskTypeYtdlp:
shortcut.CreateAndAddYtdlpTaskWithEdit(ctx, selectedStorage, dirPath, data.YtdlpURLs, msgID, userID) shortcut.CreateAndAddYtdlpTaskWithEdit(ctx, selectedStorage, dirPath, data.YtdlpURLs, data.YtdlpFlags, msgID, userID)
default: default:
return fmt.Errorf("unexcept task type: %s", data.TaskType) return fmt.Errorf("unexcept task type: %s", data.TaskType)
} }
+2 -1
View File
@@ -51,7 +51,8 @@ func BuildAddSelectStorageKeyboard(stors []storage.Storage, adddata tcbdata.Add)
DirectLinks: adddata.DirectLinks, DirectLinks: adddata.DirectLinks,
Aria2URIs: adddata.Aria2URIs, Aria2URIs: adddata.Aria2URIs,
YtdlpURLs: adddata.YtdlpURLs, YtdlpURLs: adddata.YtdlpURLs,
YtdlpFlags: adddata.YtdlpFlags,
} }
dataid := xid.New().String() dataid := xid.New().String()
err := cache.Set(dataid, data) err := cache.Set(dataid, data)
+3 -2
View File
@@ -15,7 +15,7 @@ import (
"github.com/krau/SaveAny-Bot/storage" "github.com/krau/SaveAny-Bot/storage"
) )
func CreateAndAddYtdlpTaskWithEdit(ctx *ext.Context, stor storage.Storage, dirPath string, urls []string, msgID int, userID int64) error { func CreateAndAddYtdlpTaskWithEdit(ctx *ext.Context, stor storage.Storage, dirPath string, urls []string, flags []string, msgID int, userID int64) error {
logger := log.FromContext(ctx) logger := log.FromContext(ctx)
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx) injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
@@ -29,13 +29,14 @@ func CreateAndAddYtdlpTaskWithEdit(ctx *ext.Context, stor storage.Storage, dirPa
return dispatcher.EndGroups return dispatcher.EndGroups
} }
logger.Infof("Creating yt-dlp task for %d URL(s)", len(urls)) logger.Infof("Creating yt-dlp task for %d URL(s) with %d flag(s)", len(urls), len(flags))
// Create yt-dlp task // Create yt-dlp task
task := ytdlp.NewTask( task := ytdlp.NewTask(
xid.New().String(), xid.New().String(),
injectCtx, injectCtx,
urls, urls,
flags,
stor, stor,
stor.JoinStoragePath(dirPath), stor.JoinStoragePath(dirPath),
ytdlp.NewProgress(msgID, userID), ytdlp.NewProgress(msgID, userID),
+36 -13
View File
@@ -7,7 +7,6 @@ import (
"github.com/celestix/gotgproto/dispatcher" "github.com/celestix/gotgproto/dispatcher"
"github.com/celestix/gotgproto/ext" "github.com/celestix/gotgproto/ext"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/duke-git/lancet/v2/slice"
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem" "github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
"github.com/krau/SaveAny-Bot/common/i18n" "github.com/krau/SaveAny-Bot/common/i18n"
@@ -25,29 +24,53 @@ func handleYtdlpCmd(ctx *ext.Context, update *ext.Update) error {
return dispatcher.EndGroups return dispatcher.EndGroups
} }
urls := args[1:] // Separate URLs and flags from arguments
// Validate and clean URLs var urls []string
for i, link := range urls { var flags []string
urls[i] = strings.TrimSpace(link)
u, err := url.Parse(link) for i := 1; i < len(args); i++ {
if err != nil || u.Scheme == "" || u.Host == "" { arg := strings.TrimSpace(args[i])
logger.Warnf("Invalid URL: %s", link) if arg == "" {
urls[i] = "" continue
}
// Check if it's a flag (starts with - or --)
if strings.HasPrefix(arg, "-") {
flags = append(flags, arg)
// Check if the next argument is a value for this flag (not starting with -)
if i+1 < len(args) && !strings.HasPrefix(strings.TrimSpace(args[i+1]), "-") {
nextArg := strings.TrimSpace(args[i+1])
// Only treat as flag value if it's not a valid URL
u, err := url.Parse(nextArg)
if err != nil || u.Scheme == "" || u.Host == "" {
flags = append(flags, nextArg)
i++ // Skip the next argument as it's been consumed
continue
}
}
} else {
// Try to parse as URL
u, err := url.Parse(arg)
if err != nil || u.Scheme == "" || u.Host == "" {
logger.Warnf("Invalid URL: %s", arg)
continue
}
urls = append(urls, arg)
} }
} }
urls = slice.Compact(urls)
if len(urls) == 0 { if len(urls) == 0 {
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgYtdlpErrorNoValidUrls)), nil) ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgYtdlpErrorNoValidUrls)), nil)
return dispatcher.EndGroups return dispatcher.EndGroups
} }
logger.Debugf("Preparing yt-dlp download for %d URL(s)", len(urls)) logger.Debugf("Preparing yt-dlp download for %d URL(s) with %d flag(s)", len(urls), len(flags))
// Build storage selection keyboard // Build storage selection keyboard
markup, err := msgelem.BuildAddSelectStorageKeyboard(storage.GetUserStorages(ctx, update.GetUserChat().GetID()), tcbdata.Add{ markup, err := msgelem.BuildAddSelectStorageKeyboard(storage.GetUserStorages(ctx, update.GetUserChat().GetID()), tcbdata.Add{
TaskType: tasktype.TaskTypeYtdlp, TaskType: tasktype.TaskTypeYtdlp,
YtdlpURLs: urls, YtdlpURLs: urls,
YtdlpFlags: flags,
}) })
if err != nil { if err != nil {
return err return err
+1 -1
View File
@@ -289,7 +289,7 @@ bot:
error_no_valid_links: "No valid links to download" error_no_valid_links: "No valid links to download"
info_files_select_storage: "Total {{.Count}} files, please select storage" info_files_select_storage: "Total {{.Count}} files, please select storage"
ytdlp: ytdlp:
usage: "Usage: /ytdlp <URL1> <URL2> ..." usage: "Usage: /ytdlp [OPTIONS] <URL1> [URL2] ...\nExamples:\n /ytdlp https://example.com/video\n /ytdlp --format best https://example.com/video\n /ytdlp --extract-audio --audio-format mp3 https://example.com/video"
error_no_valid_urls: "No valid URLs" error_no_valid_urls: "No valid URLs"
info_urls_select_storage: "Found {{.Count}} links, please select storage" info_urls_select_storage: "Found {{.Count}} links, please select storage"
info_downloading: "Downloading via yt-dlp..." info_downloading: "Downloading via yt-dlp..."
+1 -1
View File
@@ -290,7 +290,7 @@ bot:
error_no_valid_links: "没有有效的链接可供下载" error_no_valid_links: "没有有效的链接可供下载"
info_files_select_storage: "共 {{.Count}} 个文件, 请选择存储位置" info_files_select_storage: "共 {{.Count}} 个文件, 请选择存储位置"
ytdlp: ytdlp:
usage: "用法: /ytdlp <URL1> <URL2> ..." usage: "用法: /ytdlp [选项] <URL1> [URL2] ...\n示例:\n /ytdlp https://example.com/video\n /ytdlp --format best https://example.com/video\n /ytdlp --extract-audio --audio-format mp3 https://example.com/video"
error_no_valid_urls: "没有有效的 URL" error_no_valid_urls: "没有有效的 URL"
info_urls_select_storage: "共 {{.Count}} 个链接, 请选择存储位置" info_urls_select_storage: "共 {{.Count}} 个链接, 请选择存储位置"
info_downloading: "正在通过 yt-dlp 下载..." info_downloading: "正在通过 yt-dlp 下载..."
+17 -8
View File
@@ -80,22 +80,31 @@ func (t *Task) Execute(ctx context.Context) error {
func (t *Task) downloadFiles(ctx context.Context, tempDir string) ([]string, error) { func (t *Task) downloadFiles(ctx context.Context, tempDir string) ([]string, error) {
logger := log.FromContext(ctx) logger := log.FromContext(ctx)
// Configure yt-dlp command // Configure yt-dlp command with default settings
cmd := ytdlp.New(). cmd := ytdlp.New().
FormatSort("res,ext:mp4:m4a"). Output(filepath.Join(tempDir, "%(title)s.%(ext)s"))
RecodeVideo("mp4").
Output(filepath.Join(tempDir, "%(title)s.%(ext)s")). // If no custom flags are provided, use default behavior
RestrictFilenames() if len(t.Flags) == 0 {
cmd = cmd.
FormatSort("res,ext:mp4:m4a").
RecodeVideo("mp4").
RestrictFilenames()
}
if t.Progress != nil { if t.Progress != nil {
t.Progress.OnProgress(ctx, t, "Downloading...") t.Progress.OnProgress(ctx, t, "Downloading...")
} }
// Execute download with URLs as arguments // Execute download with URLs and custom flags
logger.Infof("Executing yt-dlp for %d URL(s)", len(t.URLs)) logger.Infof("Executing yt-dlp for %d URL(s) with %d custom flag(s)", len(t.URLs), len(t.Flags))
// Combine URLs and flags as arguments
// The Run method will pass flags as raw command-line arguments
args := append(t.Flags, t.URLs...)
// Run with context for cancellation support // Run with context for cancellation support
result, err := cmd.Run(ctx, t.URLs...) result, err := cmd.Run(ctx, args...)
if err != nil { if err != nil {
// Check if context was canceled // Check if context was canceled
if errors.Is(err, context.Canceled) { if errors.Is(err, context.Canceled) {
+3
View File
@@ -15,6 +15,7 @@ type Task struct {
ID string ID string
ctx context.Context ctx context.Context
URLs []string URLs []string
Flags []string
Storage storage.Storage Storage storage.Storage
StorPath string StorPath string
Progress ProgressTracker Progress ProgressTracker
@@ -43,6 +44,7 @@ func NewTask(
id string, id string,
ctx context.Context, ctx context.Context,
urls []string, urls []string,
flags []string,
stor storage.Storage, stor storage.Storage,
storPath string, storPath string,
progressTracker ProgressTracker, progressTracker ProgressTracker,
@@ -51,6 +53,7 @@ func NewTask(
ID: id, ID: id,
ctx: ctx, ctx: ctx,
URLs: urls, URLs: urls,
Flags: flags,
Storage: stor, Storage: stor,
StorPath: storPath, StorPath: storPath,
Progress: progressTracker, Progress: progressTracker,
+2 -1
View File
@@ -48,7 +48,8 @@ type Add struct {
// aria2 // aria2
Aria2URIs []string Aria2URIs []string
// ytdlp // ytdlp
YtdlpURLs []string YtdlpURLs []string
YtdlpFlags []string
} }
type SetDefaultStorage struct { type SetDefaultStorage struct {