mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-05-22 00:30:36 +08:00
Merge pull request #204 from Rain-kl/feat/save-strategy
新增功能: 重名文件时选择保存策略 - 重命名,覆盖,跳过
This commit is contained in:
@@ -73,14 +73,16 @@ func handleAddCallback(ctx *ext.Context, update *ext.Update) error {
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
dirPath = dir.Path
|
||||
} else if data.SelectedDirPath != "" {
|
||||
dirPath = data.SelectedDirPath
|
||||
}
|
||||
|
||||
switch data.TaskType {
|
||||
case tasktype.TaskTypeTgfiles:
|
||||
if data.AsBatch {
|
||||
return shortcut.CreateAndAddBatchTGFileTaskWithEdit(ctx, userID, selectedStorage, dirPath, data.Files, msgID)
|
||||
return shortcut.CreateAndAddBatchTGFileTaskWithEdit(ctx, userID, selectedStorage, dirPath, data.Files, msgID, data.ConflictStrategy)
|
||||
}
|
||||
return shortcut.CreateAndAddTGFileTaskWithEdit(ctx, userID, selectedStorage, dirPath, data.Files[0], msgID)
|
||||
return shortcut.CreateAndAddTGFileTaskWithEdit(ctx, userID, selectedStorage, dirPath, data.Files[0], msgID, data.ConflictStrategy)
|
||||
case tasktype.TaskTypeTphpics:
|
||||
return shortcut.CreateAndAddtelegraphWithEdit(ctx, userID, data.TphPageNode, data.TphDirPath, data.TphPics, selectedStorage, msgID)
|
||||
case tasktype.TaskTypeParseditem:
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/celestix/gotgproto/dispatcher"
|
||||
"github.com/celestix/gotgproto/ext"
|
||||
"github.com/gotd/td/tg"
|
||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/conflictutil"
|
||||
"github.com/krau/SaveAny-Bot/common/i18n"
|
||||
"github.com/krau/SaveAny-Bot/common/i18n/i18nk"
|
||||
"github.com/krau/SaveAny-Bot/config"
|
||||
@@ -26,6 +27,10 @@ func handleConfigCmd(ctx *ext.Context, update *ext.Update) error {
|
||||
Text: i18n.T(i18nk.BotMsgConfigButtonFilenameStrategy),
|
||||
Data: fmt.Appendf(nil, "%s %s", tcbdata.TypeConfig, "fnamest"),
|
||||
},
|
||||
&tg.KeyboardButtonCallback{
|
||||
Text: i18n.T(i18nk.BotMsgConfigButtonConflictStrategy),
|
||||
Data: fmt.Appendf(nil, "%s %s", tcbdata.TypeConfig, "conflictst"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -51,6 +56,8 @@ func handleConfigCallback(ctx *ext.Context, update *ext.Update) error {
|
||||
switch args[1] {
|
||||
case "fnamest":
|
||||
return handleConfigFnameSTCallback(ctx, update)
|
||||
case "conflictst":
|
||||
return handleConfigConflictSTCallback(ctx, update)
|
||||
default:
|
||||
return invaildDataAnswer()
|
||||
}
|
||||
@@ -110,6 +117,55 @@ func handleConfigFnameSTCallback(ctx *ext.Context, update *ext.Update) error {
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
func handleConfigConflictSTCallback(ctx *ext.Context, update *ext.Update) error {
|
||||
userID := update.CallbackQuery.GetUserID()
|
||||
user, err := database.GetUserByChatID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args := strings.Fields(string(update.CallbackQuery.Data))
|
||||
if len(args) == 3 {
|
||||
selected := args[2]
|
||||
if !tcbdata.IsConflictStrategy(selected) {
|
||||
return fmt.Errorf("invalid conflict strategy: %s", selected)
|
||||
}
|
||||
user.ConflictStrategy = selected
|
||||
if err := database.UpdateUser(ctx, user); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: update.CallbackQuery.GetMsgID(),
|
||||
Message: i18n.T(i18nk.BotMsgConfigInfoConflictStrategySet, map[string]any{
|
||||
"Strategy": conflictutil.Display(selected),
|
||||
}),
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
opts := tcbdata.ConflictStrategyValues()
|
||||
rows := make([]tg.KeyboardButtonRow, 0, len(opts))
|
||||
for _, opt := range opts {
|
||||
rows = append(rows, tg.KeyboardButtonRow{
|
||||
Buttons: []tg.KeyboardButtonClass{
|
||||
&tg.KeyboardButtonCallback{
|
||||
Text: conflictutil.Display(opt),
|
||||
Data: fmt.Appendf(nil, "%s %s %s", tcbdata.TypeConfig, "conflictst", opt),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
markup := &tg.ReplyInlineMarkup{Rows: rows}
|
||||
currentSt := conflictutil.EffectiveStrategy(user)
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: update.CallbackQuery.GetMsgID(),
|
||||
Message: i18n.T(i18nk.BotMsgConfigPromptSelectConflictStrategy, map[string]any{
|
||||
"Strategy": conflictutil.Display(currentSt),
|
||||
}),
|
||||
ReplyMarkup: markup,
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
func handleConfigFnameTmpl(ctx *ext.Context, update *ext.Update) error {
|
||||
userID := update.GetUserChat().GetID()
|
||||
user, err := database.GetUserByChatID(ctx, userID)
|
||||
|
||||
55
client/bot/handlers/utils/conflictutil/conflict.go
Normal file
55
client/bot/handlers/utils/conflictutil/conflict.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package conflictutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/krau/SaveAny-Bot/common/i18n"
|
||||
"github.com/krau/SaveAny-Bot/common/i18n/i18nk"
|
||||
"github.com/krau/SaveAny-Bot/database"
|
||||
"github.com/krau/SaveAny-Bot/pkg/tcbdata"
|
||||
)
|
||||
|
||||
const maxConflictLines = 10
|
||||
|
||||
func EffectiveStrategy(user *database.User) string {
|
||||
if user != nil && tcbdata.IsConflictStrategy(user.ConflictStrategy) {
|
||||
return user.ConflictStrategy
|
||||
}
|
||||
return tcbdata.ConflictStrategyRename
|
||||
}
|
||||
|
||||
func ResolveStrategy(user *database.User, override string) string {
|
||||
if tcbdata.IsConflictStrategy(override) {
|
||||
return override
|
||||
}
|
||||
return EffectiveStrategy(user)
|
||||
}
|
||||
|
||||
func Display(strategy string) string {
|
||||
switch strategy {
|
||||
case tcbdata.ConflictStrategyRename:
|
||||
return i18n.T(i18nk.BotMsgConfigConflictStrategyRename, nil)
|
||||
case tcbdata.ConflictStrategyAsk:
|
||||
return i18n.T(i18nk.BotMsgConfigConflictStrategyAsk, nil)
|
||||
case tcbdata.ConflictStrategyOverwrite:
|
||||
return i18n.T(i18nk.BotMsgConfigConflictStrategyOverwrite, nil)
|
||||
case tcbdata.ConflictStrategySkip:
|
||||
return i18n.T(i18nk.BotMsgConfigConflictStrategySkip, nil)
|
||||
default:
|
||||
return strategy
|
||||
}
|
||||
}
|
||||
|
||||
func FormatPaths(conflicts []string) string {
|
||||
if len(conflicts) <= maxConflictLines {
|
||||
return strings.Join(conflicts, "\n")
|
||||
}
|
||||
return strings.Join(conflicts[:maxConflictLines], "\n") + "\n" + i18n.T(i18nk.BotMsgCommonPromptConflictMoreFiles, map[string]any{
|
||||
"Count": len(conflicts) - maxConflictLines,
|
||||
})
|
||||
}
|
||||
|
||||
func FormatPath(storageName, storagePath string) string {
|
||||
return fmt.Sprintf("[%s]:%s", storageName, storagePath)
|
||||
}
|
||||
@@ -38,6 +38,8 @@ func BuildAddSelectStorageKeyboard(stors []storage.Storage, adddata tcbdata.Add)
|
||||
data := tcbdata.Add{
|
||||
TaskType: taskType,
|
||||
SelectedStorName: storage.Name(),
|
||||
SelectedDirPath: adddata.SelectedDirPath,
|
||||
ConflictStrategy: adddata.ConflictStrategy,
|
||||
|
||||
Files: adddata.Files,
|
||||
AsBatch: len(adddata.Files) > 1,
|
||||
@@ -109,6 +111,38 @@ func BuildAddOneSelectStorageMessage(ctx context.Context, stors []storage.Storag
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BuildConflictStrategyMarkup(adddata tcbdata.Add) (*tg.ReplyInlineMarkup, error) {
|
||||
type option struct {
|
||||
text string
|
||||
strategy string
|
||||
}
|
||||
options := []option{
|
||||
{text: i18n.T(i18nk.BotMsgCommonButtonConflictRename, nil), strategy: tcbdata.ConflictStrategyRename},
|
||||
{text: i18n.T(i18nk.BotMsgCommonButtonConflictOverwrite, nil), strategy: tcbdata.ConflictStrategyOverwrite},
|
||||
{text: i18n.T(i18nk.BotMsgCommonButtonConflictSkip, nil), strategy: tcbdata.ConflictStrategySkip},
|
||||
}
|
||||
buttons := make([]tg.KeyboardButtonClass, 0, len(options))
|
||||
for _, opt := range options {
|
||||
data := adddata
|
||||
data.ConflictStrategy = opt.strategy
|
||||
dataid := xid.New().String()
|
||||
if err := cache.Set(dataid, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buttons = append(buttons, &tg.KeyboardButtonCallback{
|
||||
Text: opt.text,
|
||||
Data: fmt.Appendf(nil, "%s %s", tcbdata.TypeAdd, dataid),
|
||||
})
|
||||
}
|
||||
rows := make([]tg.KeyboardButtonRow, 0, len(buttons))
|
||||
for _, button := range buttons {
|
||||
rows = append(rows, tg.KeyboardButtonRow{
|
||||
Buttons: []tg.KeyboardButtonClass{button},
|
||||
})
|
||||
}
|
||||
return &tg.ReplyInlineMarkup{Rows: rows}, nil
|
||||
}
|
||||
|
||||
// Builds the inline keyboard for setting default storage
|
||||
func BuildSetDefaultStorageMarkup(
|
||||
ctx context.Context,
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/celestix/gotgproto/ext"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/gotd/td/tg"
|
||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/conflictutil"
|
||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
|
||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/ruleutil"
|
||||
"github.com/krau/SaveAny-Bot/common/i18n"
|
||||
@@ -17,14 +18,17 @@ import (
|
||||
"github.com/krau/SaveAny-Bot/core/tasks/batchtfile"
|
||||
tftask "github.com/krau/SaveAny-Bot/core/tasks/tfile"
|
||||
"github.com/krau/SaveAny-Bot/database"
|
||||
"github.com/krau/SaveAny-Bot/pkg/enums/tasktype"
|
||||
"github.com/krau/SaveAny-Bot/pkg/tcbdata"
|
||||
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||
"github.com/krau/SaveAny-Bot/storage"
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
|
||||
// 创建一个 tfile.TGFileTask 并添加到任务队列中, 以编辑消息的方式反馈结果
|
||||
func CreateAndAddTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage.Storage, dirPath string, file tfile.TGFileMessage, trackMsgID int) error {
|
||||
func CreateAndAddTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage.Storage, dirPath string, file tfile.TGFileMessage, trackMsgID int, conflictStrategy ...string) error {
|
||||
logger := log.FromContext(ctx)
|
||||
strategy := selectedConflictStrategy(conflictStrategy)
|
||||
user, err := database.GetUserByChatID(ctx, userID)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to get user by chat ID: %s", err)
|
||||
@@ -36,6 +40,7 @@ func CreateAndAddTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
strategy = conflictutil.ResolveStrategy(user, strategy)
|
||||
if user.ApplyRule && user.Rules != nil {
|
||||
matched, matchedStorageName, matchedDirPath := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
||||
if !matched {
|
||||
@@ -60,7 +65,26 @@ func CreateAndAddTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage
|
||||
}
|
||||
startCreateTask:
|
||||
storagePath := path.Join(dirPath, file.Name())
|
||||
if strategy == tcbdata.ConflictStrategyAsk || strategy == tcbdata.ConflictStrategySkip {
|
||||
exists := stor.Exists(ctx, storagePath)
|
||||
if exists && strategy == tcbdata.ConflictStrategyAsk {
|
||||
return promptTGFileConflictStrategy(ctx, userID, stor.Name(), dirPath, []tfile.TGFileMessage{file}, false, []string{conflictutil.FormatPath(stor.Name(), storagePath)}, trackMsgID)
|
||||
}
|
||||
if exists {
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: trackMsgID,
|
||||
Message: i18n.T(i18nk.BotMsgCommonInfoAllConflictFilesSkipped, map[string]any{
|
||||
"Skipped": file.Name(),
|
||||
}),
|
||||
ReplyMarkup: nil,
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
}
|
||||
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
||||
if strategy == tcbdata.ConflictStrategyOverwrite {
|
||||
injectCtx = storage.WithOverwrite(injectCtx)
|
||||
}
|
||||
taskid := xid.New().String()
|
||||
task, err := tftask.NewTGFileTask(taskid, injectCtx, file, stor, storagePath,
|
||||
tftask.NewProgressTrack(
|
||||
@@ -97,8 +121,9 @@ startCreateTask:
|
||||
}
|
||||
|
||||
// 创建一个 batchtfile.BatchTGFileTask 并添加到任务队列中, 以编辑消息的方式反馈结果
|
||||
func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage.Storage, dirPath string, files []tfile.TGFileMessage, trackMsgID int) error {
|
||||
func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage.Storage, dirPath string, files []tfile.TGFileMessage, trackMsgID int, conflictStrategy ...string) error {
|
||||
logger := log.FromContext(ctx)
|
||||
strategy := selectedConflictStrategy(conflictStrategy)
|
||||
user, err := database.GetUserByChatID(ctx, userID)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to get user by chat ID: %s", err)
|
||||
@@ -110,6 +135,7 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
strategy = conflictutil.ResolveStrategy(user, strategy)
|
||||
|
||||
useRule := user.ApplyRule && user.Rules != nil
|
||||
|
||||
@@ -128,14 +154,17 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
||||
return storname, dirP
|
||||
}
|
||||
|
||||
skipped := make([]string, 0)
|
||||
conflicts := make([]string, 0)
|
||||
elems := make([]batchtfile.TaskElement, 0, len(files))
|
||||
type albumFile struct {
|
||||
file tfile.TGFileMessage
|
||||
storage storage.Storage
|
||||
dirPath string
|
||||
}
|
||||
albumFiles := make(map[int64][]albumFile, 0)
|
||||
for _, file := range files {
|
||||
storName, dirPath := applyRule(file)
|
||||
storName, matchedDirPath := applyRule(file)
|
||||
fileStor := stor
|
||||
if storName != stor.Name() && storName != "" {
|
||||
fileStor, err = storage.GetStorageByUserIDAndName(ctx, user.ChatID, storName)
|
||||
@@ -150,8 +179,19 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
}
|
||||
if !dirPath.NeedNewForAlbum() {
|
||||
storPath := path.Join(dirPath.String(), file.Name())
|
||||
if !matchedDirPath.NeedNewForAlbum() {
|
||||
storPath := path.Join(matchedDirPath.String(), file.Name())
|
||||
if strategy == tcbdata.ConflictStrategyAsk || strategy == tcbdata.ConflictStrategySkip {
|
||||
exists := fileStor.Exists(ctx, storPath)
|
||||
if exists && strategy == tcbdata.ConflictStrategyAsk {
|
||||
conflicts = append(conflicts, conflictutil.FormatPath(fileStor.Name(), storPath))
|
||||
continue
|
||||
}
|
||||
if exists {
|
||||
skipped = append(skipped, file.Name())
|
||||
continue
|
||||
}
|
||||
}
|
||||
elem, err := batchtfile.NewTaskElement(fileStor, storPath, file)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to create task element: %s", err)
|
||||
@@ -170,12 +210,17 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
||||
logger.Warnf("File %s is not in a group, skipping album handling", file.Name())
|
||||
continue
|
||||
}
|
||||
fileDirPath := matchedDirPath.String()
|
||||
if matchedDirPath.NeedNewForAlbum() {
|
||||
fileDirPath = dirPath
|
||||
}
|
||||
if _, ok := albumFiles[groupId]; !ok {
|
||||
albumFiles[groupId] = make([]albumFile, 0)
|
||||
}
|
||||
albumFiles[groupId] = append(albumFiles[groupId], albumFile{
|
||||
file: file,
|
||||
storage: fileStor,
|
||||
dirPath: fileDirPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -188,7 +233,18 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
||||
albumDir := strings.TrimSuffix(path.Base(afiles[0].file.Name()), path.Ext(afiles[0].file.Name()))
|
||||
albumStor := afiles[0].storage
|
||||
for _, af := range afiles {
|
||||
afstorPath := path.Join(dirPath, albumDir, af.file.Name())
|
||||
afstorPath := path.Join(af.dirPath, albumDir, af.file.Name())
|
||||
if strategy == tcbdata.ConflictStrategyAsk || strategy == tcbdata.ConflictStrategySkip {
|
||||
exists := albumStor.Exists(ctx, afstorPath)
|
||||
if exists && strategy == tcbdata.ConflictStrategyAsk {
|
||||
conflicts = append(conflicts, conflictutil.FormatPath(albumStor.Name(), afstorPath))
|
||||
continue
|
||||
}
|
||||
if exists {
|
||||
skipped = append(skipped, af.file.Name())
|
||||
continue
|
||||
}
|
||||
}
|
||||
elem, err := batchtfile.NewTaskElement(albumStor, afstorPath, af.file)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to create task element for album file: %s", err)
|
||||
@@ -204,9 +260,26 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
||||
}
|
||||
}
|
||||
|
||||
if strategy == tcbdata.ConflictStrategyAsk && len(conflicts) > 0 {
|
||||
return promptTGFileConflictStrategy(ctx, userID, stor.Name(), dirPath, files, true, conflicts, trackMsgID)
|
||||
}
|
||||
|
||||
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
||||
if strategy == tcbdata.ConflictStrategyOverwrite {
|
||||
injectCtx = storage.WithOverwrite(injectCtx)
|
||||
}
|
||||
if len(elems) == 0 {
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: trackMsgID,
|
||||
Message: i18n.T(i18nk.BotMsgCommonInfoAllConflictFilesSkipped, map[string]any{
|
||||
"Skipped": strings.Join(skipped, "\n"),
|
||||
}),
|
||||
ReplyMarkup: nil,
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
taskid := xid.New().String()
|
||||
task := batchtfile.NewBatchTGFileTask(taskid, injectCtx, elems, batchtfile.NewProgressTracker(trackMsgID, userID), true)
|
||||
task := batchtfile.NewBatchTGFileTask(taskid, injectCtx, elems, batchtfile.NewProgressTrackerWithSkipped(trackMsgID, userID, skipped), true)
|
||||
if err := core.AddTask(injectCtx, task); err != nil {
|
||||
logger.Errorf("Failed to add batch task: %s", err)
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
@@ -218,11 +291,48 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: trackMsgID,
|
||||
Message: i18n.T(i18nk.BotMsgCommonInfoBatchTasksAdded, map[string]any{
|
||||
"Count": len(files),
|
||||
}),
|
||||
ID: trackMsgID,
|
||||
Message: buildBatchAddedMessage(len(elems), skipped),
|
||||
ReplyMarkup: nil,
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
func promptTGFileConflictStrategy(ctx *ext.Context, userID int64, storageName, dirPath string, files []tfile.TGFileMessage, asBatch bool, conflicts []string, trackMsgID int) error {
|
||||
markup, err := msgelem.BuildConflictStrategyMarkup(tcbdata.Add{
|
||||
TaskType: tasktype.TaskTypeTgfiles,
|
||||
SelectedStorName: storageName,
|
||||
SettedDir: true,
|
||||
SelectedDirPath: dirPath,
|
||||
Files: files,
|
||||
AsBatch: asBatch,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||
ID: trackMsgID,
|
||||
Message: i18n.T(i18nk.BotMsgCommonPromptSelectConflictStrategy, map[string]any{"Files": conflictutil.FormatPaths(conflicts)}),
|
||||
ReplyMarkup: markup,
|
||||
})
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
|
||||
func selectedConflictStrategy(strategies []string) string {
|
||||
if len(strategies) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strategies[0]
|
||||
}
|
||||
|
||||
func buildBatchAddedMessage(count int, skipped []string) string {
|
||||
if len(skipped) == 0 {
|
||||
return i18n.T(i18nk.BotMsgCommonInfoBatchTasksAdded, map[string]any{
|
||||
"Count": count,
|
||||
})
|
||||
}
|
||||
return i18n.T(i18nk.BotMsgCommonInfoBatchTasksAddedWithSkipped, map[string]any{
|
||||
"Count": count,
|
||||
"Skipped": strings.Join(skipped, "\n"),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user